From 809600018b350a6cd20f43d05e98fb9531c90f86 Mon Sep 17 00:00:00 2001 From: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> Date: Wed, 27 Sep 2023 12:16:34 -0300 Subject: [PATCH] filesharing: use mediaplayer GitLab: #485 Change-Id: Ie3f129cd0cee14a97764eb22ee2b5f530a3f3023 --- CMakeLists.txt | 2 +- bin/dbus/cx.ring.Ring.VideoManager.xml | 109 ++++++++++++++++++++++ bin/dbus/dbusvideomanager.hpp | 51 ++++++++++ configure.ac | 2 +- meson.build | 2 +- src/client/videomanager.cpp | 61 ++++++++---- src/client/videomanager.h | 6 +- src/jami/videomanager_interface.h | 10 +- src/media/audio/audio_input.h | 2 +- src/media/media_decoder.cpp | 1 + src/media/media_decoder.h | 1 + src/media/media_player.cpp | 38 +++++--- src/media/media_player.h | 6 +- src/media/media_recorder.cpp | 41 +++++--- src/media/media_stream.h | 10 ++ src/media/video/video_input.cpp | 50 ++++++---- src/media/video/video_input.h | 11 +-- src/media/video/video_rtp_session.cpp | 13 +-- src/sip/sipcall.cpp | 32 +++++++ src/sip/sipcall.h | 2 + test/unitTest/call/recorder.cpp | 23 +++-- test/unitTest/media/test_media_player.cpp | 44 +++++---- 22 files changed, 406 insertions(+), 111 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 759b5b587d..6fef36dccc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16) project(jami - VERSION 13.9.0 + VERSION 13.10.0 LANGUAGES C CXX) set(PACKAGE_NAME "Jami Daemon") set (CMAKE_CXX_STANDARD 17) diff --git a/bin/dbus/cx.ring.Ring.VideoManager.xml b/bin/dbus/cx.ring.Ring.VideoManager.xml index fac32e447c..8e27e54db1 100644 --- a/bin/dbus/cx.ring.Ring.VideoManager.xml +++ b/bin/dbus/cx.ring.Ring.VideoManager.xml @@ -160,6 +160,103 @@ </arg> </method> + <method name="createMediaPlayer" tp:name-for-bindings="createMediaPlayer"> + <tp:added version="13.10.0"/> + <tp:docstring>Create a media player</tp:docstring> + <arg type="s" name="path" direction="in"> + <tp:docstring>File path</tp:docstring> + </arg> + <arg type="s" name="id" direction="out"> + <tp:docstring>Media player id</tp:docstring> + </arg> + </method> + + <method name="closeMediaPlayer" tp:name-for-bindings="closeMediaPlayer"> + <tp:added version="13.10.0"/> + <tp:docstring>Close media player</tp:docstring> + <arg type="s" name="id" direction="in"> + <tp:docstring>Media player id</tp:docstring> + </arg> + <arg type="b" name="state" direction="out"> + <tp:docstring>true if successfully closed</tp:docstring> + </arg> + </method> + + <method name="pausePlayer" tp:name-for-bindings="pausePlayer"> + <tp:added version="13.10.0"/> + <tp:docstring>Pause media player</tp:docstring> + <arg type="s" name="id" direction="in"> + <tp:docstring>Media player id</tp:docstring> + </arg> + <arg type="b" name="pause" direction="in"> + <tp:docstring>true if wishes to pause player</tp:docstring> + </arg> + <arg type="b" name="state" direction="out"> + <tp:docstring>true if successfully paused</tp:docstring> + </arg> + </method> + + <method name="mutePlayerAudio" tp:name-for-bindings="mutePlayerAudio"> + <tp:added version="13.10.0"/> + <tp:docstring>Mute media player audio</tp:docstring> + <arg type="s" name="id" direction="in"> + <tp:docstring>Media player id</tp:docstring> + </arg> + <arg type="b" name="mute" direction="in"> + <tp:docstring>true if wishes to pause player</tp:docstring> + </arg> + <arg type="b" name="state" direction="out"> + <tp:docstring>true if successfully muted</tp:docstring> + </arg> + </method> + + <method name="playerSeekToTime" tp:name-for-bindings="playerSeekToTime"> + <tp:added version="13.10.0"/> + <tp:docstring>Seek media player position</tp:docstring> + <arg type="s" name="id" direction="in"> + <tp:docstring>Media player id</tp:docstring> + </arg> + <arg type="i" name="time" direction="in"> + <tp:docstring>timestamp</tp:docstring> + </arg> + <arg type="b" name="state" direction="out"> + <tp:docstring>true if successfully seeked</tp:docstring> + </arg> + </method> + + <method name="getPlayerPosition" tp:name-for-bindings="getPlayerPosition"> + <tp:added version="13.10.0"/> + <tp:docstring>Get media player position</tp:docstring> + <arg type="s" name="id" direction="in"> + <tp:docstring>Media player id</tp:docstring> + </arg> + <arg type="x" name="position" direction="out"> + <tp:docstring>position in timestamp</tp:docstring> + </arg> + </method> + + <method name="getPlayerDuration" tp:name-for-bindings="getPlayerDuration"> + <tp:added version="13.10.0"/> + <tp:docstring>Get media player position</tp:docstring> + <arg type="s" name="id" direction="in"> + <tp:docstring>Media player id</tp:docstring> + </arg> + <arg type="x" name="duration" direction="out"> + <tp:docstring>duration in timestamp</tp:docstring> + </arg> + </method> + + <method name="setAutoRestart" tp:name-for-bindings="setAutoRestart"> + <tp:added version="13.10.0"/> + <tp:docstring>Restar media player after it reaches the end</tp:docstring> + <arg type="s" name="id" direction="in"> + <tp:docstring>Media player id</tp:docstring> + </arg> + <arg type="b" name="restart" direction="in"> + <tp:docstring>true if media player is to restart automaticaly</tp:docstring> + </arg> + </method> + <signal name="deviceEvent" tp:name-for-bindings="deviceEvent"> <tp:docstring>Signal triggered by changes in the detected v4l2 devices, e.g. a camera being unplugged.</tp:docstring> </signal> @@ -195,5 +292,17 @@ <tp:docstring>Whether or not this texture belongs to a video mixer or is a single texture</tp:docstring> </arg> </signal> + + <signal name="fileOpened" tp:name-for-bindings="fileOpened"> + <tp:added version="13.10.0"/> + <tp:docstring>Notify clients of a new media player infos</tp:docstring> + <arg type="s" name="playerId"> + <tp:docstring>Media player id</tp:docstring> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="MapStringString"/> + <arg type="a{ss}" name="info"> + <tp:docstring>Info from the media player</tp:docstring> + </arg> + </signal> </interface> </node> diff --git a/bin/dbus/dbusvideomanager.hpp b/bin/dbus/dbusvideomanager.hpp index ab3cd647aa..b4e2d5d2ab 100644 --- a/bin/dbus/dbusvideomanager.hpp +++ b/bin/dbus/dbusvideomanager.hpp @@ -151,6 +151,54 @@ public: libjami::stopLocalRecorder(filepath); } + std::string + createMediaPlayer(const std::string& path) + { + return libjami::createMediaPlayer(path); + } + + bool + pausePlayer(const std::string& id, const bool& pause) + { + return libjami::pausePlayer(id, pause); + } + + bool + closeMediaPlayer(const std::string& id) + { + return libjami::closeMediaPlayer(id); + } + + bool + mutePlayerAudio(const std::string& id, const bool& mute) + { + return libjami::mutePlayerAudio(id, mute); + } + + bool + playerSeekToTime(const std::string& id, const int& time) + { + return libjami::playerSeekToTime(id, time); + } + + int64_t + getPlayerPosition(const std::string& id) + { + return libjami::getPlayerPosition(id); + } + + int64_t + getPlayerDuration(const std::string& id) + { + return libjami::getPlayerDuration(id); + } + + void + setAutoRestart(const std::string& id, const bool& restart) + { + libjami::setAutoRestart(id, restart); + } + private: void @@ -160,6 +208,7 @@ private: using libjami::exportable_serialized_callback; using libjami::VideoSignal; + using libjami::MediaPlayerSignal; using SharedCallback = std::shared_ptr<libjami::CallbackWrapperBase>; const std::map<std::string, SharedCallback> videoEvHandlers = { @@ -169,6 +218,8 @@ private: std::bind(&DBusVideoManager::emitDecodingStarted, this, _1, _2, _3, _4, _5)), exportable_serialized_callback<VideoSignal::DecodingStopped>( std::bind(&DBusVideoManager::emitDecodingStopped, this, _1, _2, _3)), + exportable_serialized_callback<MediaPlayerSignal::FileOpened>( + std::bind(&DBusVideoManager::emitFileOpened, this, _1, _2)), }; libjami::registerSignalHandlers(videoEvHandlers); diff --git a/configure.ac b/configure.ac index a693c7e305..236fc40429 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ dnl Jami - configure.ac dnl Process this file with autoconf to produce a configure script. AC_PREREQ([2.69]) -AC_INIT([Jami Daemon],[13.9.0],[jami@gnu.org],[jami]) +AC_INIT([Jami Daemon],[13.10.0],[jami@gnu.org],[jami]) dnl Clear the implicit flags that default to '-g -O2', otherwise they dnl take precedence over the values we set via the diff --git a/meson.build b/meson.build index 5ac983f5dd..e61b933f12 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('jami-daemon', ['c', 'cpp'], - version: '13.9.0', + version: '13.10.0', license: 'GPL3+', default_options: ['cpp_std=gnu++17', 'buildtype=debugoptimized'], meson_version:'>= 0.56' diff --git a/src/client/videomanager.cpp b/src/client/videomanager.cpp index 224cc7525c..d7a56c5670 100644 --- a/src/client/videomanager.cpp +++ b/src/client/videomanager.cpp @@ -571,25 +571,25 @@ createMediaPlayer(const std::string& path) } bool -pausePlayer(const std::string& id, bool pause) +closeMediaPlayer(const std::string& id) { - return jami::pausePlayer(id, pause); + return jami::closeMediaPlayer(id); } bool -closeMediaPlayer(const std::string& id) +pausePlayer(const std::string& id, const bool& pause) { - return jami::closeMediaPlayer(id); + return jami::pausePlayer(id, pause); } bool -mutePlayerAudio(const std::string& id, bool mute) +mutePlayerAudio(const std::string& id, const bool& mute) { return jami::mutePlayerAudio(id, mute); } bool -playerSeekToTime(const std::string& id, int time) +playerSeekToTime(const std::string& id, const int& time) { return jami::playerSeekToTime(id, time); } @@ -600,6 +600,18 @@ getPlayerPosition(const std::string& id) return jami::getPlayerPosition(id); } +int64_t +getPlayerDuration(const std::string& id) +{ + return jami::getPlayerDuration(id); +} + +void +setAutoRestart(const std::string& id, const bool& restart) +{ + jami::setAutoRestart(id, restart); +} + bool getDecodingAccelerated() { @@ -678,19 +690,20 @@ getVideoDeviceMonitor() } std::shared_ptr<video::VideoInput> -getVideoInput(const std::string& id, video::VideoInputMode inputMode) +getVideoInput(const std::string& id, video::VideoInputMode inputMode, const std::string& sink) { + auto sinkId = sink.empty() ? id : sink; auto& vmgr = Manager::instance().getVideoManager(); std::lock_guard<std::mutex> lk(vmgr.videoMutex); - auto it = vmgr.videoInputs.find(id); + auto it = vmgr.videoInputs.find(sinkId); if (it != vmgr.videoInputs.end()) { if (auto input = it->second.lock()) { return input; } } - auto input = std::make_shared<video::VideoInput>(inputMode, id); - vmgr.videoInputs[id] = input; + auto input = std::make_shared<video::VideoInput>(inputMode, id, sinkId); + vmgr.videoInputs[sinkId] = input; return input; } @@ -748,13 +761,14 @@ getMediaPlayer(const std::string& id) std::string createMediaPlayer(const std::string& path) { - auto player = std::make_shared<MediaPlayer>(path); - if (!player->isInputValid()) { - return ""; + auto player = getMediaPlayer(path); + if (!player) { + player = std::make_shared<MediaPlayer>(path); + } else { + return path; } - auto playerId = player.get()->getId(); - Manager::instance().getVideoManager().mediaPlayers[playerId] = player; - return playerId; + Manager::instance().getVideoManager().mediaPlayers[path] = player; + return path; } bool @@ -799,4 +813,19 @@ getPlayerPosition(const std::string& id) return -1; } +int64_t +getPlayerDuration(const std::string& id) +{ + if (auto player = getMediaPlayer(id)) + return player->getPlayerDuration(); + return -1; +} + +void +setAutoRestart(const std::string& id, bool restart) +{ + if (auto player = getMediaPlayer(id)) + player->setAutoRestart(restart); +} + } // namespace jami diff --git a/src/client/videomanager.h b/src/client/videomanager.h index bde76f42f7..81b5a72652 100644 --- a/src/client/videomanager.h +++ b/src/client/videomanager.h @@ -71,7 +71,9 @@ public: #ifdef ENABLE_VIDEO video::VideoDeviceMonitor& getVideoDeviceMonitor(); std::shared_ptr<video::VideoInput> getVideoInput( - const std::string& id, video::VideoInputMode inputMode = video::VideoInputMode::Undefined); + const std::string& id, + video::VideoInputMode inputMode = video::VideoInputMode::Undefined, + const std::string& sink = ""); #endif std::shared_ptr<AudioInput> getAudioInput(const std::string& id); std::string createMediaPlayer(const std::string& path); @@ -81,5 +83,7 @@ bool closeMediaPlayer(const std::string& id); bool mutePlayerAudio(const std::string& id, bool mute); bool playerSeekToTime(const std::string& id, int time); int64_t getPlayerPosition(const std::string& id); +int64_t getPlayerDuration(const std::string& id); +void setAutoRestart(const std::string& id, bool restart); } // namespace jami diff --git a/src/jami/videomanager_interface.h b/src/jami/videomanager_interface.h index 0e5bd77b6c..a1d822a156 100644 --- a/src/jami/videomanager_interface.h +++ b/src/jami/videomanager_interface.h @@ -199,10 +199,12 @@ LIBJAMI_PUBLIC bool closeVideoInput(const std::string& id); LIBJAMI_PUBLIC std::string createMediaPlayer(const std::string& path); LIBJAMI_PUBLIC bool closeMediaPlayer(const std::string& id); -LIBJAMI_PUBLIC bool pausePlayer(const std::string& id, bool pause); -LIBJAMI_PUBLIC bool mutePlayerAudio(const std::string& id, bool mute); -LIBJAMI_PUBLIC bool playerSeekToTime(const std::string& id, int time); -int64_t getPlayerPosition(const std::string& id); +LIBJAMI_PUBLIC bool pausePlayer(const std::string& id, const bool& pause); +LIBJAMI_PUBLIC bool mutePlayerAudio(const std::string& id, const bool& mute); +LIBJAMI_PUBLIC bool playerSeekToTime(const std::string& id, const int& time); +LIBJAMI_PUBLIC int64_t getPlayerPosition(const std::string& id); +LIBJAMI_PUBLIC int64_t getPlayerDuration(const std::string& id); +LIBJAMI_PUBLIC void setAutoRestart(const std::string& id, const bool& restart); LIBJAMI_PUBLIC bool registerSinkTarget(const std::string& sinkId, SinkTarget target); #ifdef ENABLE_SHM diff --git a/src/media/audio/audio_input.h b/src/media/audio/audio_input.h index e9d1d81265..d17d3bcbdd 100644 --- a/src/media/audio/audio_input.h +++ b/src/media/audio/audio_input.h @@ -83,7 +83,7 @@ private: void frameResized(std::shared_ptr<AudioFrame>&& ptr); std::string id_; - bool muteState_ = false; + bool muteState_ {false}; uint64_t sent_samples = 0; mutable std::mutex fmtMutex_ {}; AudioFormat format_; diff --git a/src/media/media_decoder.cpp b/src/media/media_decoder.cpp index 224ad352cf..6dcaa471ae 100644 --- a/src/media/media_decoder.cpp +++ b/src/media/media_decoder.cpp @@ -24,6 +24,7 @@ #include "media_decoder.h" #include "media_device.h" #include "media_buffer.h" +#include "media_const.h" #include "media_io_handle.h" #include "audio/ringbuffer.h" #include "audio/resampler.h" diff --git a/src/media/media_decoder.h b/src/media/media_decoder.h index dd8e5418b9..340c2e32d7 100644 --- a/src/media/media_decoder.h +++ b/src/media/media_decoder.h @@ -41,6 +41,7 @@ #include "media_device.h" #include "media_stream.h" #include "media_buffer.h" +#include "media_attribute.h" #include "noncopyable.h" #include <map> diff --git a/src/media/media_player.cpp b/src/media/media_player.cpp index f4250c5727..fd0c971886 100644 --- a/src/media/media_player.cpp +++ b/src/media/media_player.cpp @@ -28,26 +28,29 @@ namespace jami { static constexpr auto MS_PER_PACKET = std::chrono::milliseconds(20); -MediaPlayer::MediaPlayer(const std::string& path) +MediaPlayer::MediaPlayer(const std::string& resource) : loop_(std::bind(&MediaPlayer::configureMediaInputs, this), std::bind(&MediaPlayer::process, this), [] {}) { + auto suffix = resource; static const std::string& sep = libjami::Media::VideoProtocolPrefix::SEPARATOR; - const auto pos = path.find(sep); - const auto suffix = path.substr(pos + sep.size()); + const auto pos = resource.find(sep); + if (pos != std::string::npos) { + suffix = resource.substr(pos + sep.size()); + } + + path_ = suffix; if (access(suffix.c_str(), R_OK) != 0) { - JAMI_ERR() << "File '" << path << "' not available"; + JAMI_ERR() << "File '" << path_ << "' not available"; return; } - path_ = path; - id_ = std::to_string(rand()); - audioInput_ = jami::getAudioInput(id_); + audioInput_ = jami::getAudioInput(path_); audioInput_->setPaused(paused_); #ifdef ENABLE_VIDEO - videoInput_ = jami::getVideoInput(id_, video::VideoInputMode::ManagedByDaemon); + videoInput_ = jami::getVideoInput(path_, video::VideoInputMode::ManagedByDaemon, resource); videoInput_->setPaused(paused_); #endif @@ -57,7 +60,10 @@ MediaPlayer::MediaPlayer(const std::string& path) MediaPlayer::~MediaPlayer() { + pause(true); loop_.join(); + audioInput_.reset(); + videoInput_.reset(); } bool @@ -92,10 +98,8 @@ MediaPlayer::configureMediaInputs() try { videoStream_ = demuxer_->selectStream(AVMEDIA_TYPE_VIDEO); if (hasVideo()) { - videoInput_->setSink(id_); videoInput_->configureFilePlayback(path_, demuxer_, videoStream_); videoInput_->updateStartTime(startTime_); - muteAudio(true); } } catch (const std::exception& e) { videoInput_ = nullptr; @@ -165,13 +169,13 @@ MediaPlayer::emitInfo() std::map<std::string, std::string> info {{"duration", std::to_string(fileDuration_)}, {"audio_stream", std::to_string(audioStream_)}, {"video_stream", std::to_string(videoStream_)}}; - emitSignal<libjami::MediaPlayerSignal::FileOpened>(id_, info); + emitSignal<libjami::MediaPlayerSignal::FileOpened>(path_, info); } bool MediaPlayer::isInputValid() { - return !id_.empty(); + return !path_.empty(); } void @@ -262,6 +266,8 @@ MediaPlayer::playFileFromBeginning() videoInput_->updateStartTime(startTime_); } #endif + if (autoRestart_) + pause(false); } void @@ -281,7 +287,7 @@ MediaPlayer::flushMediaBuffers() const std::string& MediaPlayer::getId() const { - return id_; + return path_; } int64_t @@ -293,6 +299,12 @@ MediaPlayer::getPlayerPosition() const return av_gettime() - startTime_ - pauseInterval_; } +int64_t +MediaPlayer::getPlayerDuration() const +{ + return fileDuration_; +} + bool MediaPlayer::isPaused() const { diff --git a/src/media/media_player.h b/src/media/media_player.h index 1af887bbbd..d6f141b23a 100644 --- a/src/media/media_player.h +++ b/src/media/media_player.h @@ -31,7 +31,7 @@ namespace jami { class MediaPlayer { public: - MediaPlayer(const std::string& path); + MediaPlayer(const std::string& resource); ~MediaPlayer(); void pause(bool pause); @@ -40,11 +40,13 @@ public: void muteAudio(bool mute); bool seekToTime(int64_t time); int64_t getPlayerPosition() const; + int64_t getPlayerDuration() const; bool isPaused() const; + void setAutoRestart(bool state) { autoRestart_ = state; } private: std::string path_; - std::string id_; + bool autoRestart_ {false}; // media inputs #ifdef ENABLE_VIDEO diff --git a/src/media/media_recorder.cpp b/src/media/media_recorder.cpp index 2a4c9101d3..69a10ba967 100644 --- a/src/media/media_recorder.cpp +++ b/src/media/media_recorder.cpp @@ -276,7 +276,16 @@ MediaRecorder::addStream(const MediaStream& ms) it = streams_.insert(std::make_pair(ms.name, std::move(streamPtr))).first; JAMI_LOG("[Recorder: {:p}] Recorder input #{}: {:s}", fmt::ptr(this), streams_.size(), ms.name); } else { - JAMI_LOG("[Recorder: {:p}] Recorder already has '{:s}' as input", fmt::ptr(this), ms.name); + if (ms == it->second->info) + JAMI_LOG("[Recorder: {:p}] Recorder already has '{:s}' as input", fmt::ptr(this), ms.name); + else { + it->second + = std::make_unique<StreamObserver>(ms, + [this, + ms](const std::shared_ptr<MediaFrame>& frame) { + onFrame(ms.name, frame); + }); + } } if (ms.isVideo) @@ -354,15 +363,17 @@ MediaRecorder::onFrame(const std::string& name, const std::shared_ptr<MediaFrame ms.timeBase, static_cast<AVRounding>(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); - std::unique_ptr<MediaFrame> filteredFrame; + std::vector<std::unique_ptr<MediaFrame>> filteredFrames; #ifdef ENABLE_VIDEO if (ms.isVideo && videoFilter_ && outputVideoFilter_) { std::lock_guard<std::mutex> lk(mutexFilterVideo_); - videoFilter_->feedInput(clone->pointer(), name); + videoFilter_->feedInput(clone->pointer(), name); auto videoFilterOutput = videoFilter_->readOutput(); if (videoFilterOutput) { outputVideoFilter_->feedInput(videoFilterOutput->pointer(), "input"); - filteredFrame = outputVideoFilter_->readOutput(); + while (auto fFrame = outputVideoFilter_->readOutput()) { + filteredFrames.emplace_back(std::move(fFrame)); + } } } else if (audioFilter_ && outputAudioFilter_) { #endif // ENABLE_VIDEO @@ -371,15 +382,15 @@ MediaRecorder::onFrame(const std::string& name, const std::shared_ptr<MediaFrame auto audioFilterOutput = audioFilter_->readOutput(); if (audioFilterOutput) { outputAudioFilter_->feedInput(audioFilterOutput->pointer(), "input"); - filteredFrame = outputAudioFilter_->readOutput(); + filteredFrames.emplace_back(outputAudioFilter_->readOutput()); } #ifdef ENABLE_VIDEO } #endif // ENABLE_VIDEO - if (filteredFrame) { + for (auto& fFrame : filteredFrames) { std::lock_guard<std::mutex> lk(mutexFrameBuff_); - frameBuff_.emplace_back(std::move(filteredFrame)); + frameBuff_.emplace_back(std::move(fFrame)); cv_.notify_one(); } } @@ -543,9 +554,11 @@ MediaRecorder::setupVideoOutput() if (scaledHeight > 720) scaleFilter += ",scale=-2:720"; - ret = outputVideoFilter_->initialize( - "[input]" + scaleFilter + ",pad=1280:720:(ow-iw)/2:(oh-ih)/2,format=pix_fmts=yuv420p,fps=30", - {secondaryFilter}); + std::ostringstream f; + f << "[input]" << scaleFilter + << ",pad=1280:720:(ow-iw)/2:(oh-ih)/2,format=pix_fmts=yuv420p,fps=30"; + + ret = outputVideoFilter_->initialize(f.str(), {secondaryFilter}); if (ret < 0) { JAMI_ERR() << "Failed to initialize output video filter"; @@ -569,17 +582,17 @@ MediaRecorder::buildVideoFilter(const std::vector<MediaStream>& peers, case 1: { auto p = peers[0]; const constexpr int minHeight = 720; - const auto newFps = std::max(p.frameRate, local.frameRate); const bool needScale = (p.height < minHeight); const int newHeight = (needScale ? minHeight : p.height); // NOTE -2 means preserve aspect ratio and have the new number be even if (needScale) - v << "[" << p.name << "] fps=" << newFps << ", scale=-2:" << newHeight << " [v:m]; "; + v << "[" << p.name << "] fps=30, scale=-2:" << newHeight + << " [v:m]; "; else - v << "[" << p.name << "] fps=" << newFps << " [v:m]; "; + v << "[" << p.name << "] fps=30 [v:m]; "; - v << "[" << local.name << "] fps=" << newFps << ", scale=-2:" << newHeight / 5 + v << "[" << local.name << "] fps=30, scale=-2:" << newHeight / 5 << " [v:o]; "; v << "[v:m] [v:o] overlay=main_w-overlay_w:main_h-overlay_h" diff --git a/src/media/media_stream.h b/src/media/media_stream.h index 8162554b52..b2bb35a593 100644 --- a/src/media/media_stream.h +++ b/src/media/media_stream.h @@ -150,6 +150,16 @@ struct MediaStream frameSize = f->nb_samples; } } + + friend bool operator==(const MediaStream& ms1, const MediaStream& ms2) + { + return ms1.bitrate == ms2.bitrate and ms1.firstTimestamp == ms2.firstTimestamp + and ms1.format == ms2.format and ms1.frameRate == ms2.frameRate + and ms1.frameSize == ms2.frameSize and ms1.height == ms2.height + and ms1.isVideo == ms2.isVideo and ms1.name == ms2.name + and ms1.nbChannels == ms2.nbChannels and ms1.sampleRate == ms2.sampleRate + and ms1.timeBase == ms2.timeBase and ms1.width == ms2.width; + } }; inline std::ostream& diff --git a/src/media/video/video_input.cpp b/src/media/video/video_input.cpp index 8670ca4889..fe4235bd60 100644 --- a/src/media/video/video_input.cpp +++ b/src/media/video/video_input.cpp @@ -55,7 +55,7 @@ namespace video { static constexpr unsigned default_grab_width = 640; static constexpr unsigned default_grab_height = 480; -VideoInput::VideoInput(VideoInputMode inputMode, const std::string& id_) +VideoInput::VideoInput(VideoInputMode inputMode, const std::string& resource, const std::string& sink) : VideoGenerator::VideoGenerator() , loop_(std::bind(&VideoInput::setup, this), std::bind(&VideoInput::process, this), @@ -69,8 +69,8 @@ VideoInput::VideoInput(VideoInputMode inputMode, const std::string& id_) inputMode_ = VideoInputMode::ManagedByDaemon; #endif } - sink_ = Manager::instance().createSinkClient(id_); - switchInput(id_); + sink_ = Manager::instance().createSinkClient(sink.empty() ? resource : sink); + switchInput(resource); } VideoInput::~VideoInput() @@ -249,10 +249,29 @@ VideoInput::configureFilePlayback(const std::string&, decoder_ = std::move(decoder); playingFile_ = true; - loop_.start(); + // For DBUS it is imperative that we start the sink before setting the frame size + sink_->start(); /* Signal the client about readable sink */ sink_->setFrameSize(decoder_->getWidth(), decoder_->getHeight()); + + loop_.start(); + + decOpts_.width = ((decoder_->getWidth() >> 3) << 3); + decOpts_.height = ((decoder_->getHeight() >> 3) << 3); + decOpts_.framerate = decoder_->getFps(); + AVPixelFormat fmt = decoder_->getPixelFormat(); + if (fmt != AV_PIX_FMT_NONE) { + decOpts_.pixel_format = av_get_pix_fmt_name(fmt); + } else { + JAMI_WARN("Could not determine pixel format, using default"); + decOpts_.pixel_format = av_get_pix_fmt_name(AV_PIX_FMT_YUV420P); + } + + if (onSuccessfulSetup_) + onSuccessfulSetup_(MEDIA_VIDEO, 0); + foundDecOpts(decOpts_); + futureDecOpts_ = foundDecOpts_.get_future().share(); } void @@ -579,7 +598,7 @@ void VideoInput::restart() { if (loop_.isStopping()) - switchInput(currentResource_); + switchInput(resource_); } std::shared_future<DeviceParams> @@ -592,14 +611,14 @@ VideoInput::switchInput(const std::string& resource) return {}; } - currentResource_ = resource; + resource_ = resource; decOptsFound_ = false; std::promise<DeviceParams> p; foundDecOpts_.swap(p); // Switch off video input? - if (resource.empty()) { + if (resource_.empty()) { clearOptions(); futureDecOpts_ = foundDecOpts_.get_future(); startLoop(); @@ -609,15 +628,15 @@ VideoInput::switchInput(const std::string& resource) // Supported MRL schemes static const std::string sep = libjami::Media::VideoProtocolPrefix::SEPARATOR; - const auto pos = resource.find(sep); + const auto pos = resource_.find(sep); if (pos == std::string::npos) return {}; - const auto prefix = resource.substr(0, pos); - if ((pos + sep.size()) >= resource.size()) + const auto prefix = resource_.substr(0, pos); + if ((pos + sep.size()) >= resource_.size()) return {}; - const auto suffix = resource.substr(pos + sep.size()); + const auto suffix = resource_.substr(pos + sep.size()); bool ready = false; @@ -680,18 +699,13 @@ VideoInput::setSink(const std::string& sinkId) } void -VideoInput::setFrameSize(const int width, const int height) +VideoInput::setupSink(const int width, const int height) { + setup(); /* Signal the client about readable sink */ sink_->setFrameSize(width, height); } -void -VideoInput::setupSink() -{ - setup(); -} - void VideoInput::stopSink() { diff --git a/src/media/video/video_input.h b/src/media/video/video_input.h index 658c16f8d0..bf6126e326 100644 --- a/src/media/video/video_input.h +++ b/src/media/video/video_input.h @@ -57,11 +57,12 @@ class VideoInput : public VideoGenerator { public: VideoInput(VideoInputMode inputMode = VideoInputMode::Undefined, - const std::string& id_ = "local"); + const std::string& resource = "local", + const std::string& sink = ""); ~VideoInput(); // as VideoGenerator - const std::string& getName() const { return currentResource_; } + const std::string& getName() const { return resource_; } int getWidth() const; int getHeight() const; AVPixelFormat getPixelFormat() const; @@ -83,8 +84,7 @@ public: void flushBuffers(); void setPaused(bool paused) { paused_ = paused; } void setSeekTime(int64_t time); - void setFrameSize(const int width, const int height); - void setupSink(); + void setupSink(const int width, const int height); void stopSink(); void setRecorderCallback(const std::function<void(const MediaStream& ms)>& cb); @@ -117,8 +117,7 @@ private: std::shared_future<DeviceParams> switchInput(const std::string& resource); - std::string id_; - std::string currentResource_; + std::string resource_; std::atomic<bool> switchPending_ = {false}; std::atomic_bool isStopped_ = {false}; diff --git a/src/media/video/video_rtp_session.cpp b/src/media/video/video_rtp_session.cpp index b016c4b37f..3c2e125e32 100644 --- a/src/media/video/video_rtp_session.cpp +++ b/src/media/video/video_rtp_session.cpp @@ -28,6 +28,7 @@ #include "socket_pair.h" #include "sip/sipvoiplink.h" // for enqueueKeyframeRequest #include "manager.h" +#include "media_const.h" #ifdef ENABLE_PLUGIN #include "plugin/streamdata.h" #include "plugin/jamipluginmanager.h" @@ -137,9 +138,8 @@ VideoRtpSession::startSender() } if (not conference_) { - auto input = getVideoInput(input_); - videoLocal_ = input; - if (input) { + videoLocal_ = getVideoInput(input_); + if (videoLocal_) { videoLocal_->setRecorderCallback( [w=weak_from_this()](const MediaStream& ms) { Manager::instance().ioContext()->post([w=std::move(w), ms]() { @@ -147,7 +147,7 @@ VideoRtpSession::startSender() shared->attachLocalRecorder(ms); }); }); - auto newParams = input->getParams(); + auto newParams = videoLocal_->getParams(); try { if (newParams.valid() && newParams.wait_for(NEWPARAMS_TIMEOUT) == std::future_status::ready) { @@ -166,10 +166,7 @@ VideoRtpSession::startSender() } #if (defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)) - if (auto input1 = std::static_pointer_cast<VideoInput>(videoLocal_)) { - input1->setupSink(); - input1->setFrameSize(localVideoParams_.width, localVideoParams_.height); - } + videoLocal_->setupSink(localVideoParams_.width, localVideoParams_.height); #endif } diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp index 46a7b0f882..05658b9143 100644 --- a/src/sip/sipcall.cpp +++ b/src/sip/sipcall.cpp @@ -71,6 +71,8 @@ #include "tracepoint.h" +#include "media/media_decoder.h" + namespace jami { using sip_utils::CONST_PJ_STR; @@ -160,6 +162,8 @@ SIPCall::~SIPCall() setSipTransport({}); setInviteSession(); // prevents callback usage + + closeMediaPlayer(mediaPlayerId_); } int @@ -2502,6 +2506,34 @@ SIPCall::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList) { std::lock_guard<std::recursive_mutex> lk {callMutex_}; auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, isSrtpEnabled()); + bool hasFileSharing {false}; + + for (const auto& media : mediaAttrList) { + if (!media.enabled_ || media.sourceUri_.empty()) + continue; + + // Supported MRL schemes + static const std::string sep = libjami::Media::VideoProtocolPrefix::SEPARATOR; + + const auto pos = media.sourceUri_.find(sep); + if (pos == std::string::npos) + continue; + + const auto prefix = media.sourceUri_.substr(0, pos); + if ((pos + sep.size()) >= media.sourceUri_.size()) + continue; + + if (prefix == libjami::Media::VideoProtocolPrefix::FILE) { + hasFileSharing = true; + mediaPlayerId_ = media.sourceUri_; + createMediaPlayer(mediaPlayerId_); + } + } + + if (!hasFileSharing) { + closeMediaPlayer(mediaPlayerId_); + mediaPlayerId_ = ""; + } // Disable video if disabled in the account. auto account = getSIPAccount(); diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h index 74c7436d79..8dee71e1b6 100644 --- a/src/sip/sipcall.h +++ b/src/sip/sipcall.h @@ -526,6 +526,8 @@ private: #ifdef ENABLE_VIDEO int rotation_ {0}; #endif + + std::string mediaPlayerId_ {}; }; // Helpers diff --git a/test/unitTest/call/recorder.cpp b/test/unitTest/call/recorder.cpp index 05042c5e27..7d91495043 100644 --- a/test/unitTest/call/recorder.cpp +++ b/test/unitTest/call/recorder.cpp @@ -75,17 +75,19 @@ public: void setUp(); void tearDown(); - std::string aliceId; - std::string bobId; - std::string recordDir; - std::string recordedFile; + std::string aliceId {}; + std::string bobId {}; + std::string recordDir {}; + std::string recordedFile {}; + std::string playerId {}; + std::shared_ptr<MediaPlayer> player {}; CallData bobCall {}; std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; std::condition_variable cv; - std::string videoPath = std::filesystem::absolute("media/test_video_file.mp4").string(); + std::string videoPath = "file://" + std::filesystem::absolute("media/test_video_file.mp4").string(); private: void registerSignalHandlers(); @@ -118,6 +120,10 @@ RecorderTest::setUp() aliceId = actors["alice"]; bobId = actors["bob"]; bobCall.reset(); + playerId = jami::createMediaPlayer(videoPath); + player = jami::getMediaPlayer(playerId); + player->setAutoRestart(true); + player->pause(false); libjami::setRecordPath(recordDir); } @@ -127,6 +133,7 @@ RecorderTest::tearDown() { libjami::setIsAlwaysRecording(false); dhtnet::fileutils::removeAll(recordDir); + player.reset(); wait_for_removal_of({aliceId, bobId}); } @@ -210,7 +217,7 @@ RecorderTest::testRecordCall() {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR}, {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR}, {libjami::Media::MediaAttributeKey::LABEL, "video_0"}, - {libjami::Media::MediaAttributeKey::SOURCE, "file://" + videoPath}}; + {libjami::Media::MediaAttributeKey::SOURCE, videoPath}}; mediaList.emplace_back(mediaAttributeA); auto callId = libjami::placeCallWithMedia(aliceId, bobUri, mediaList); CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !bobCall.callId.empty(); })); @@ -406,7 +413,7 @@ RecorderTest::testStopCallWhileRecording() {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR}, {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR}, {libjami::Media::MediaAttributeKey::LABEL, "video_0"}, - {libjami::Media::MediaAttributeKey::SOURCE, "file://" + videoPath}}; + {libjami::Media::MediaAttributeKey::SOURCE, videoPath}}; mediaList.emplace_back(mediaAttributeA); mediaList.emplace_back(mediaAttributeV); auto callId = libjami::placeCallWithMedia(aliceId, bobUri, mediaList); @@ -458,7 +465,7 @@ RecorderTest::testDaemonPreference() {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR}, {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR}, {libjami::Media::MediaAttributeKey::LABEL, "video_0"}, - {libjami::Media::MediaAttributeKey::SOURCE, "file://" + videoPath}}; + {libjami::Media::MediaAttributeKey::SOURCE, videoPath}}; mediaList.emplace_back(mediaAttributeA); mediaList.emplace_back(mediaAttributeV); auto callId = libjami::placeCallWithMedia(aliceId, bobUri, mediaList); diff --git a/test/unitTest/media/test_media_player.cpp b/test/unitTest/media/test_media_player.cpp index 10d4e8e1f1..5dc719aad4 100644 --- a/test/unitTest/media/test_media_player.cpp +++ b/test/unitTest/media/test_media_player.cpp @@ -56,9 +56,9 @@ private: std::string playerId1_ {}; std::string playerId2_ {}; - std::string duration_ {}; - std::string audio_stream_ {}; - std::string video_stream_ {}; + int64_t duration_ {}; + int audio_stream_ {}; + int video_stream_ {}; std::shared_ptr<MediaPlayer> mediaPlayer {}; std::mutex mtx; @@ -79,45 +79,53 @@ MediaPlayerTest::setUp() handler.insert(libjami::exportable_callback<libjami::MediaPlayerSignal::FileOpened>( [=](const std::string& playerId, const std::map<std::string, std::string>& info) { - duration_ = info.at("duration"); - audio_stream_ = info.at("audio_stream"); - video_stream_ = info.at("video_stream"); + duration_ = std::stol(info.at("duration")); + audio_stream_ = std::stoi(info.at("audio_stream")); + video_stream_ = std::stoi(info.at("video_stream")); playerId2_ = playerId; cv.notify_all(); })); libjami::registerSignalHandlers(handler); - playerId1_ = libjami::createMediaPlayer(filePath); - mediaPlayer = Manager::instance().getVideoManager().mediaPlayers[playerId1_]; + playerId1_ = jami::createMediaPlayer(filePath); + mediaPlayer = jami::getMediaPlayer(playerId1_); cv.wait_for(lk, 5s); } void MediaPlayerTest::tearDown() { + jami::closeMediaPlayer(playerId1_); + mediaPlayer.reset(); + playerId1_ = {}; + playerId2_ = {}; libjami::fini(); } void MediaPlayerTest::testCreate() { + JAMI_INFO() << "Start testCreate"; CPPUNIT_ASSERT(playerId1_ == playerId2_); CPPUNIT_ASSERT(mediaPlayer->getId() == playerId1_); CPPUNIT_ASSERT(mediaPlayer->isInputValid()); - CPPUNIT_ASSERT(audio_stream_ != "-1"); - CPPUNIT_ASSERT(video_stream_ != "-1"); + CPPUNIT_ASSERT(audio_stream_ != -1); + CPPUNIT_ASSERT(video_stream_ != -1); CPPUNIT_ASSERT(mediaPlayer->isPaused()); CPPUNIT_ASSERT(mediaPlayer->getPlayerPosition() == 0); + JAMI_INFO() << "End testCreate"; } void MediaPlayerTest::testPause() { - mediaPlayer->pause(true); + JAMI_INFO() << "Start testPause"; + // should start paused CPPUNIT_ASSERT(mediaPlayer->isPaused()); mediaPlayer->pause(false); CPPUNIT_ASSERT(!mediaPlayer->isPaused()); + JAMI_INFO() << "End testPause"; } bool @@ -129,10 +137,9 @@ MediaPlayerTest::isWithinUsec(int64_t currentTime, int64_t seekTime, int64_t mar void MediaPlayerTest::testSeekWhilePaused() { - mediaPlayer->pause(true); + JAMI_INFO() << "Start testSeekWhilePaused"; int64_t startTime = mediaPlayer->getPlayerPosition(); - int64_t duration = std::stoi(duration_); CPPUNIT_ASSERT(mediaPlayer->seekToTime(startTime+100)); CPPUNIT_ASSERT(isWithinUsec(mediaPlayer->getPlayerPosition(), startTime+100, 1)); @@ -143,18 +150,20 @@ MediaPlayerTest::testSeekWhilePaused() CPPUNIT_ASSERT(mediaPlayer->seekToTime(startTime+500)); CPPUNIT_ASSERT(isWithinUsec(mediaPlayer->getPlayerPosition(), startTime+500, 1)); - CPPUNIT_ASSERT(mediaPlayer->seekToTime(duration-1)); - CPPUNIT_ASSERT(isWithinUsec(mediaPlayer->getPlayerPosition(), duration-1, 1)); + CPPUNIT_ASSERT(mediaPlayer->seekToTime(duration_-1)); + CPPUNIT_ASSERT(isWithinUsec(mediaPlayer->getPlayerPosition(), duration_-1, 1)); CPPUNIT_ASSERT(mediaPlayer->seekToTime(0)); CPPUNIT_ASSERT(isWithinUsec(mediaPlayer->getPlayerPosition(), 0, 1)); - CPPUNIT_ASSERT(!(mediaPlayer->seekToTime(duration+1))); + CPPUNIT_ASSERT(!(mediaPlayer->seekToTime(duration_+1))); + JAMI_INFO() << "End testSeekWhilePaused"; } void MediaPlayerTest::testSeekWhilePlaying() { + JAMI_INFO() << "Start testSeekWhilePlaying"; mediaPlayer->pause(false); int64_t startTime = mediaPlayer->getPlayerPosition(); @@ -172,7 +181,8 @@ MediaPlayerTest::testSeekWhilePlaying() CPPUNIT_ASSERT(mediaPlayer->seekToTime(0)); CPPUNIT_ASSERT(isWithinUsec(mediaPlayer->getPlayerPosition(), 0, 50)); - CPPUNIT_ASSERT(!(mediaPlayer->seekToTime(std::stoi(duration_)+1))); + CPPUNIT_ASSERT(!(mediaPlayer->seekToTime(duration_+1))); + JAMI_INFO() << "End testSeekWhilePlaying"; } }} // namespace jami::test -- GitLab