diff --git a/CMakeLists.txt b/CMakeLists.txt index 6fef36dcccf4c8c116ff792b8d3f34be76a085cf..2dd71d125ea42afd9366902370e3141276696668 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16) project(jami - VERSION 13.10.0 + VERSION 13.11.0 LANGUAGES C CXX) set(PACKAGE_NAME "Jami Daemon") set (CMAKE_CXX_STANDARD 17) diff --git a/configure.ac b/configure.ac index 236fc4042923c87af5446ab2ff3ed78469d76c99..796d72c93b09796f90628d6bff4ae23562cdba19 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.10.0],[jami@gnu.org],[jami]) +AC_INIT([Jami Daemon],[13.11.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 e61b933f1231c70c9ba7e21833494c383a26122e..73c16b77456bf43885c5c9ea7da960964a61dcca 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('jami-daemon', ['c', 'cpp'], - version: '13.10.0', + version: '13.11.0', license: 'GPL3+', default_options: ['cpp_std=gnu++17', 'buildtype=debugoptimized'], meson_version:'>= 0.56' diff --git a/src/account.cpp b/src/account.cpp index ded7046ca290eeddc60a575c5f715f30cf3b7c6d..de8ca79bdee169baaf0dd7f6b214b9763ddea84e 100644 --- a/src/account.cpp +++ b/src/account.cpp @@ -443,7 +443,7 @@ Account::meetMinimumRequiredVersion(const std::vector<unsigned>& version, for (size_t i = 0; i < minRequiredVersion.size(); i++) { if (i == version.size() or version[i] < minRequiredVersion[i]) return false; - if (version[i] > minRequiredVersion[i]) + if (version[i] >= minRequiredVersion[i]) return true; } return true; diff --git a/src/call.h b/src/call.h index 67df2f886cea2fd3a977d74a878ec5c1e8b95671..565552d2d9e273887fcae3aa860f5467a80f16c5 100644 --- a/src/call.h +++ b/src/call.h @@ -371,6 +371,8 @@ public: virtual std::vector<MediaAttribute> getMediaAttributeList() const = 0; + virtual std::map<std::string, bool> getAudioStreams() const = 0; + #ifdef ENABLE_VIDEO virtual void createSinks(ConfInfo& infos) = 0; #endif diff --git a/src/client/videomanager.cpp b/src/client/videomanager.cpp index d7a56c5670854e7677ee430396e95343644b3d09..aca34eeb9f8ddaabeda8980661dce0e774a78965 100644 --- a/src/client/videomanager.cpp +++ b/src/client/videomanager.cpp @@ -690,9 +690,9 @@ getVideoDeviceMonitor() } std::shared_ptr<video::VideoInput> -getVideoInput(const std::string& id, video::VideoInputMode inputMode, const std::string& sink) +getVideoInput(const std::string& resource, video::VideoInputMode inputMode, const std::string& sink) { - auto sinkId = sink.empty() ? id : sink; + auto sinkId = sink.empty() ? resource : sink; auto& vmgr = Manager::instance().getVideoManager(); std::lock_guard<std::mutex> lk(vmgr.videoMutex); auto it = vmgr.videoInputs.find(sinkId); @@ -702,7 +702,7 @@ getVideoInput(const std::string& id, video::VideoInputMode inputMode, const std: } } - auto input = std::make_shared<video::VideoInput>(inputMode, id, sinkId); + auto input = std::make_shared<video::VideoInput>(inputMode, resource, sinkId); vmgr.videoInputs[sinkId] = input; return input; } @@ -715,7 +715,7 @@ VideoManager::setDeviceOrientation(const std::string& deviceId, int angle) #endif std::shared_ptr<AudioInput> -getAudioInput(const std::string& id) +getAudioInput(const std::string& device) { auto& vmgr = Manager::instance().getVideoManager(); std::lock_guard<std::mutex> lk(vmgr.audioMutex); @@ -728,15 +728,15 @@ getAudioInput(const std::string& id) ++it; } - auto it = vmgr.audioInputs.find(id); + auto it = vmgr.audioInputs.find(device); if (it != vmgr.audioInputs.end()) { if (auto input = it->second.lock()) { return input; } } - auto input = std::make_shared<AudioInput>(id); - vmgr.audioInputs[id] = input; + auto input = std::make_shared<AudioInput>(device); + vmgr.audioInputs[device] = input; return input; } diff --git a/src/client/videomanager.h b/src/client/videomanager.h index 81b5a72652f889d33834f6abd94facd2e61d89a2..eb03fea9ec5ee8196f69fca554f517ad7b1d229f 100644 --- a/src/client/videomanager.h +++ b/src/client/videomanager.h @@ -71,11 +71,11 @@ public: #ifdef ENABLE_VIDEO video::VideoDeviceMonitor& getVideoDeviceMonitor(); std::shared_ptr<video::VideoInput> getVideoInput( - const std::string& id, + const std::string& resource, video::VideoInputMode inputMode = video::VideoInputMode::Undefined, const std::string& sink = ""); #endif -std::shared_ptr<AudioInput> getAudioInput(const std::string& id); +std::shared_ptr<AudioInput> getAudioInput(const std::string& device); std::string createMediaPlayer(const std::string& path); std::shared_ptr<MediaPlayer> getMediaPlayer(const std::string& id); bool pausePlayer(const std::string& id, bool pause); diff --git a/src/conference.cpp b/src/conference.cpp index 551b7b35b2b4df6adfb6b00da112a0eaf81064e6..40243964bb645999d799458fb016d9084a2225ad 100644 --- a/src/conference.cpp +++ b/src/conference.cpp @@ -329,6 +329,8 @@ Conference::~Conference() #endif // ENABLE_PLUGIN if (shutdownCb_) shutdownCb_(getDuration().count()); + // do not propagate sharing from conf host to calls + closeMediaPlayer(mediaPlayerId_); jami_tracepoint(conference_end, id_.c_str()); } @@ -486,6 +488,7 @@ Conference::isMediaSourceMuted(MediaType type) const return true; } + // if one is muted, then consider that all are for (const auto& source : hostSources_) { if (source.muted_ && source.type_ == type) return true; @@ -576,14 +579,41 @@ bool Conference::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList) { if (getState() != State::ACTIVE_ATTACHED) { - JAMI_ERR("[conf %s] Request media change can be performed only in attached mode", - getConfId().c_str()); + JAMI_ERROR("[conf {}] Request media change can be performed only in attached mode", + getConfId()); return false; } JAMI_DEBUG("[conf {:s}] Request media change", getConfId()); - auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, false); + auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, false);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_ = ""; + } for (auto const& mediaAttr : mediaAttrList) { JAMI_DEBUG("[conf {:s}] New requested media: {:s}", getConfId(), mediaAttr.toString(true)); @@ -593,7 +623,9 @@ Conference::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList) for (auto const& mediaAttr : mediaAttrList) { // Find media auto oldIdx = std::find_if(hostSources_.begin(), hostSources_.end(), [&](auto oldAttr) { - return oldAttr.sourceUri_ == mediaAttr.sourceUri_ && oldAttr.type_ == mediaAttr.type_; + return oldAttr.sourceUri_ == mediaAttr.sourceUri_ + && oldAttr.type_ == mediaAttr.type_ + && oldAttr.label_ == mediaAttr.label_; }); // If video, add to newVideoInputs // NOTE: For now, only supports video @@ -617,6 +649,8 @@ Conference::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList) videoMixer_->switchInputs(newVideoInputs); #endif hostSources_ = mediaAttrList; // New medias + if (!isMuted("host"sv) && !isMediaSourceMuted(MediaType::MEDIA_AUDIO)) + bindHost(); // It's host medias, so no need to negotiate anything, but inform the client. reportMediaNegotiationStatus(); @@ -911,20 +945,7 @@ Conference::attachLocalParticipant() setState(State::ACTIVE_ATTACHED); setLocalHostDefaultMediaSource(); - auto& rbPool = Manager::instance().getRingBufferPool(); - for (const auto& participant : getParticipantList()) { - if (auto call = Manager::instance().getCallFromCallID(participant)) { - if (isMuted(call->getCallId())) - rbPool.bindHalfDuplexOut(participant, RingBufferPool::DEFAULT_ID); - else - rbPool.bindCallID(participant, RingBufferPool::DEFAULT_ID); - rbPool.flush(participant); - } - - // Reset ringbuffer's readpointers - rbPool.flush(participant); - } - rbPool.flush(RingBufferPool::DEFAULT_ID); + bindHost(); #ifdef ENABLE_VIDEO if (videoMixer_) { @@ -948,12 +969,8 @@ void Conference::detachLocalParticipant() { JAMI_INFO("Detach local participant from conference %s", id_.c_str()); - if (getState() == State::ACTIVE_ATTACHED) { - foreachCall([&](auto call) { - Manager::instance().getRingBufferPool().unBindCallID(call->getCallId(), - RingBufferPool::DEFAULT_ID); - }); + unbindHost(); #ifdef ENABLE_VIDEO if (videoMixer_) @@ -974,31 +991,39 @@ Conference::detachLocalParticipant() void Conference::bindParticipant(const std::string& participant_id) { - JAMI_INFO("Bind participant %s to conference %s", participant_id.c_str(), id_.c_str()); + JAMI_LOG("Bind participant {} to conference {}", participant_id, id_); auto& rbPool = Manager::instance().getRingBufferPool(); - for (const auto& item : getParticipantList()) { - if (participant_id != item) { - // Do not attach muted participants - if (auto call = Manager::instance().getCallFromCallID(item)) { - if (isMuted(call->getCallId())) - rbPool.bindHalfDuplexOut(item, participant_id); + // Bind each of the new participant's audio streams to each of the other participants audio streams + if (auto participantCall = getCall(participant_id)) { + auto participantStreams = participantCall->getAudioStreams(); + for (auto stream : participantStreams) { + for (const auto& other : getParticipantList()) { + auto otherCall = other != participant_id ? getCall(other) : nullptr; + if (otherCall) { + auto otherStreams = otherCall->getAudioStreams(); + for (auto otherStream : otherStreams) { + if (isMuted(other)) + rbPool.bindHalfDuplexOut(otherStream.first, stream.first); + else + rbPool.bindRingbuffers(stream.first, otherStream.first); + + rbPool.flush(otherStream.first); + } + } + } + + // Bind local participant to other participants only if the + // local is attached to the conference. + if (getState() == State::ACTIVE_ATTACHED) { + if (isMediaSourceMuted(MediaType::MEDIA_AUDIO)) + rbPool.bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, stream.first); else - rbPool.bindCallID(participant_id, item); + rbPool.bindRingbuffers(stream.first, RingBufferPool::DEFAULT_ID); + rbPool.flush(RingBufferPool::DEFAULT_ID); } } - rbPool.flush(item); - } - - // Bind local participant to other participants only if the - // local is attached to the conference. - if (getState() == State::ACTIVE_ATTACHED) { - if (isMediaSourceMuted(MediaType::MEDIA_AUDIO)) - rbPool.bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, participant_id); - else - rbPool.bindCallID(participant_id, RingBufferPool::DEFAULT_ID); - rbPool.flush(RingBufferPool::DEFAULT_ID); } } @@ -1006,7 +1031,13 @@ void Conference::unbindParticipant(const std::string& participant_id) { JAMI_INFO("Unbind participant %s from conference %s", participant_id.c_str(), id_.c_str()); - Manager::instance().getRingBufferPool().unBindAllHalfDuplexOut(participant_id); + if (auto call = getCall(participant_id)) { + auto medias = call->getAudioStreams(); + auto& rbPool = Manager::instance().getRingBufferPool(); + for (const auto& [id, muted] : medias) { + rbPool.unBindAllHalfDuplexOut(id); + } + } } void @@ -1017,20 +1048,57 @@ Conference::bindHost() auto& rbPool = Manager::instance().getRingBufferPool(); for (const auto& item : getParticipantList()) { - if (auto call = Manager::instance().getCallFromCallID(item)) { - if (isMuted(call->getCallId())) - continue; - rbPool.bindCallID(item, RingBufferPool::DEFAULT_ID); - rbPool.flush(RingBufferPool::DEFAULT_ID); + if (auto call = getCall(item)) { + auto medias = call->getAudioStreams(); + for (const auto& [id, muted] : medias) { + for (const auto& source : hostSources_) { + if (source.type_ == MediaType::MEDIA_AUDIO) { + if (source.label_ == sip_utils::DEFAULT_AUDIO_STREAMID) { + if (muted) + rbPool.bindHalfDuplexOut(id, RingBufferPool::DEFAULT_ID); + else + rbPool.bindRingbuffers(id, RingBufferPool::DEFAULT_ID); + } else { + auto buffer = source.sourceUri_; + static const std::string& sep = libjami::Media::VideoProtocolPrefix::SEPARATOR; + const auto pos = source.sourceUri_.find(sep); + if (pos != std::string::npos) + buffer = source.sourceUri_.substr(pos + sep.size()); + + if (muted) + rbPool.bindHalfDuplexOut(id, buffer); + else + rbPool.bindRingbuffers(id, buffer); + } + } + } + rbPool.flush(id); + } } } + rbPool.flush(RingBufferPool::DEFAULT_ID); } + void Conference::unbindHost() { JAMI_INFO("Unbind host from conference %s", id_.c_str()); - Manager::instance().getRingBufferPool().unBindAllHalfDuplexOut(RingBufferPool::DEFAULT_ID); + for (const auto& source : hostSources_) { + if (source.type_ == MediaType::MEDIA_AUDIO) { + if (source.label_ == sip_utils::DEFAULT_AUDIO_STREAMID) { + Manager::instance().getRingBufferPool().unBindAllHalfDuplexOut(RingBufferPool::DEFAULT_ID); + } else { + auto buffer = source.sourceUri_; + static const std::string& sep = libjami::Media::VideoProtocolPrefix::SEPARATOR; + const auto pos = source.sourceUri_.find(sep); + if (pos != std::string::npos) + buffer = source.sourceUri_.substr(pos + sep.size()); + + Manager::instance().getRingBufferPool().unBindAllHalfDuplexOut(buffer); + } + } + } } ParticipantSet @@ -1463,8 +1531,7 @@ Conference::muteParticipant(const std::string& participant_id, const bool& state } } - // NOTE: For now we only have one audio per call, and no way to only - // mute one stream + // NOTE: For now we have no way to mute only one stream if (isHost(participant_id)) muteHost(state); else if (auto call = getCallFromPeerID(participant_id)) diff --git a/src/conference.h b/src/conference.h index ad1c4298f668b9bbce06dbeb94ed1bac344c7986..20191a6628f16824414c9e5a04bd9cac9a984943 100644 --- a/src/conference.h +++ b/src/conference.h @@ -441,6 +441,7 @@ private: State confState_ {State::ACTIVE_ATTACHED}; mutable std::mutex participantsMtx_ {}; ParticipantSet participants_; + std::string mediaPlayerId_ {}; mutable std::mutex confInfoMutex_ {}; ConfInfo confInfo_ {}; diff --git a/src/manager.cpp b/src/manager.cpp index 7bfa0fdf9ec0ce2a53f7efa8cee11ed2ecdb0c8b..88796552789005d2b410fa0717ff95c1a4d76142 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -539,8 +539,15 @@ Manager::ManagerPimpl::processRemainingParticipants(Conference& conf) if (n > 1) { // Reset ringbuffer's readpointers - for (const auto& p : participants) - base_.getRingBufferPool().flush(p); + for (const auto& p : participants) { + if (auto call = base_.getCallFromCallID(p)) { + auto medias = call->getAudioStreams(); + for (const auto& media : medias) { + JAMI_DEBUG("[call:{}] Remove local audio {}", p, media.first); + base_.getRingBufferPool().flush(media.first); + } + } + } base_.getRingBufferPool().flush(RingBufferPool::DEFAULT_ID); } else if (n == 1) { @@ -683,7 +690,11 @@ Manager::ManagerPimpl::bindCallToConference(Call& call, Conference& conf) JAMI_DEBUG("[call:{}] bind to conference {} (callState={})", callId, confId, state); - base_.getRingBufferPool().unBindAll(callId); + auto medias = call.getAudioStreams(); + for (const auto& media : medias) { + JAMI_DEBUG("[call:{}] Remove local audio {}", callId, media.first); + base_.getRingBufferPool().unBindAll(media.first); + } conf.addParticipant(callId); @@ -1680,10 +1691,10 @@ void Manager::addAudio(Call& call) { const auto& callId = call.getCallId(); - JAMI_INFO("Add audio to call %s", callId.c_str()); + JAMI_LOG("Add audio to call {}", callId); if (call.isConferenceParticipant()) { - JAMI_DBG("[conf:%s] Attach local audio", callId.c_str()); + JAMI_DEBUG("[conf:{}] Attach local audio", callId); // bind to conference participant /*auto iter = pimpl_->conferenceMap_.find(callId); @@ -1691,16 +1702,21 @@ Manager::addAudio(Call& call) iter->second->bindParticipant(callId); }*/ } else { - JAMI_DBG("[call:%s] Attach audio", callId.c_str()); + JAMI_DEBUG("[call:{}] Attach audio", callId); // bind to main - getRingBufferPool().bindCallID(callId, RingBufferPool::DEFAULT_ID); + auto medias = call.getAudioStreams(); + for (const auto& media : medias) { + JAMI_DEBUG("[call:{}] Attach audio", media.first); + getRingBufferPool().bindRingbuffers(media.first, + RingBufferPool::DEFAULT_ID); + } auto oldGuard = std::move(call.audioGuard); call.audioGuard = startAudioStream(AudioDeviceType::PLAYBACK); std::lock_guard<std::mutex> lock(pimpl_->audioLayerMutex_); if (!pimpl_->audiodriver_) { - JAMI_ERR("Audio driver not initialized"); + JAMI_ERROR("Audio driver not initialized"); return; } pimpl_->audiodriver_->flushUrgent(); @@ -1712,9 +1728,11 @@ void Manager::removeAudio(Call& call) { const auto& callId = call.getCallId(); - JAMI_DBG("[call:%s] Remove local audio", callId.c_str()); - getRingBufferPool().unBindAll(callId); - call.audioGuard.reset(); + auto medias = call.getAudioStreams(); + for (const auto& media : medias) { + JAMI_DEBUG("[call:{}] Remove local audio {}", callId, media.first); + getRingBufferPool().unBindAll(media.first); + } } ScheduledExecutor& diff --git a/src/media/audio/audio_input.cpp b/src/media/audio/audio_input.cpp index db669c56a08601cb2cc4ce80887b58cce7e80984..cadfc4ef65a293062ec0938ba7acdb033f1ece25 100644 --- a/src/media/audio/audio_input.cpp +++ b/src/media/audio/audio_input.cpp @@ -47,11 +47,11 @@ AudioInput::AudioInput(const std::string& id) [this](std::shared_ptr<AudioFrame>&& f) { frameResized(std::move(f)); })) - , fileId_(id + "_file") , deviceGuard_() , loop_([] { return true; }, [this] { process(); }, [] {}) { - JAMI_DBG() << "Creating audio input with id: " << id; + JAMI_DEBUG("Creating audio input with id: {}", id_); + ringBuf_ = Manager::instance().getRingBufferPool().createRingBuffer(id_); } AudioInput::AudioInput(const std::string& id, const std::string& resource) @@ -65,7 +65,10 @@ AudioInput::~AudioInput() if (playingFile_) { Manager::instance().getRingBufferPool().unBindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_); } + ringBuf_.reset(); loop_.join(); + + Manager::instance().getRingBufferPool().flush(id_); } void @@ -106,11 +109,11 @@ AudioInput::readFromDevice() { std::lock_guard<std::mutex> lk(resourceMutex_); if (decodingFile_) - while (fileBuf_->isEmpty()) + while (ringBuf_ && ringBuf_->isEmpty()) readFromFile(); if (playingFile_) { - readFromQueue(); - return; + while (ringBuf_ && ringBuf_->isEmpty()) + readFromQueue(); } } @@ -197,24 +200,28 @@ AudioInput::configureFilePlayback(const std::string& path, int index) { decoder_.reset(); - Manager::instance().getRingBufferPool().unBindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_); - fileBuf_.reset(); devOpts_ = {}; devOpts_.input = path; devOpts_.name = path; auto decoder = std::make_unique<MediaDecoder>(demuxer, index, [this](std::shared_ptr<MediaFrame>&& frame) { - if (muteState_) { + if (muteState_) libav_utils::fillWithSilence(frame->pointer()); - return; - } - fileBuf_->put(std::static_pointer_cast<AudioFrame>(frame)); + if (ringBuf_) + ringBuf_->put(std::static_pointer_cast<AudioFrame>(frame)); }); decoder->emulateRate(); + decoder->setInterruptCallback( + [](void* data) -> int { return not static_cast<AudioInput*>(data)->isCapturing(); }, this); + + // have file audio mixed into the local buffer so it gets played + Manager::instance().getRingBufferPool().bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_); + deviceGuard_ = Manager::instance().startAudioStream(AudioDeviceType::PLAYBACK); - fileBuf_ = Manager::instance().getRingBufferPool().createRingBuffer(id_); playingFile_ = true; decoder_ = std::move(decoder); + resource_ = path; + loop_.start(); } void @@ -255,11 +262,9 @@ AudioInput::initFile(const std::string& path) JAMI_WARN() << "Cannot decode audio from file, switching back to default device"; return initDevice(""); } - fileBuf_ = Manager::instance().getRingBufferPool().createRingBuffer(fileId_); - // have file audio mixed into the call buffer so it gets sent to the peer - Manager::instance().getRingBufferPool().bindHalfDuplexOut(id_, fileId_); + // have file audio mixed into the local buffer so it gets played - Manager::instance().getRingBufferPool().bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, fileId_); + Manager::instance().getRingBufferPool().bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, id_); decodingFile_ = true; deviceGuard_ = Manager::instance().startAudioStream(AudioDeviceType::PLAYBACK); return true; @@ -271,40 +276,38 @@ AudioInput::switchInput(const std::string& resource) // Always switch inputs, even if it's the same resource, so audio will be in sync with video std::unique_lock<std::mutex> lk(resourceMutex_); - JAMI_DBG() << "Switching audio source to match '" << resource << "'"; + JAMI_DEBUG("Switching audio source from {} to {}", resource_, resource); auto oldGuard = std::move(deviceGuard_); decoder_.reset(); if (decodingFile_) { decodingFile_ = false; - Manager::instance().getRingBufferPool().unBindHalfDuplexOut(id_, fileId_); Manager::instance().getRingBufferPool().unBindHalfDuplexOut(RingBufferPool::DEFAULT_ID, - fileId_); + id_); } - fileBuf_.reset(); playingDevice_ = false; - currentResource_ = resource; + resource_ = resource; devOptsFound_ = false; std::promise<DeviceParams> p; foundDevOpts_.swap(p); - if (resource.empty()) { + if (resource_.empty()) { if (initDevice("")) foundDevOpts(devOpts_); } else { 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; if (prefix == libjami::Media::VideoProtocolPrefix::FILE) ready = initFile(suffix); @@ -358,7 +361,8 @@ AudioInput::createDecoder() } auto decoder = std::make_unique<MediaDecoder>([this](std::shared_ptr<MediaFrame>&& frame) { - fileBuf_->put(std::static_pointer_cast<AudioFrame>(frame)); + if (ringBuf_) + ringBuf_->put(std::static_pointer_cast<AudioFrame>(frame)); }); // NOTE don't emulate rate, file is read as frames are needed diff --git a/src/media/audio/audio_input.h b/src/media/audio/audio_input.h index d17d3bcbddc8a3a686c3ca39dae65b4f3b0b7114..22a58615f30bac0e240e9484d12fa110c3328471 100644 --- a/src/media/audio/audio_input.h +++ b/src/media/audio/audio_input.h @@ -73,6 +73,8 @@ public: void setRecorderCallback(const std::function<void(const MediaStream& ms)>& cb); + std::string getId() const { return id_; }; + private: void readFromDevice(); void readFromFile(); @@ -83,6 +85,7 @@ private: void frameResized(std::shared_ptr<AudioFrame>&& ptr); std::string id_; + std::shared_ptr<RingBuffer> ringBuf_; bool muteState_ {false}; uint64_t sent_samples = 0; mutable std::mutex fmtMutex_ {}; @@ -94,10 +97,7 @@ private: std::unique_ptr<AudioFrameResizer> resizer_; std::unique_ptr<MediaDecoder> decoder_; - std::string fileId_; - std::shared_ptr<RingBuffer> fileBuf_; - - std::string currentResource_; + std::string resource_; std::mutex resourceMutex_ {}; DeviceParams devOpts_; std::promise<DeviceParams> foundDevOpts_; diff --git a/src/media/audio/audio_receive_thread.cpp b/src/media/audio/audio_receive_thread.cpp index b5524c2e52efaa19e87d7bca51691ac549bff8c7..2703555bd326a708793d9cab7731c989e6a5dec8 100644 --- a/src/media/audio/audio_receive_thread.cpp +++ b/src/media/audio/audio_receive_thread.cpp @@ -33,11 +33,11 @@ namespace jami { -AudioReceiveThread::AudioReceiveThread(const std::string& id, +AudioReceiveThread::AudioReceiveThread(const std::string& streamId, const AudioFormat& format, const std::string& sdp, const uint16_t mtu) - : id_(id) + : streamId_(streamId) , format_(format) , stream_(sdp) , sdpContext_(new MediaIOHandle(sdp.size(), false, &readFunction, 0, 0, this)) @@ -90,7 +90,8 @@ AudioReceiveThread::setup() return false; } - ringbuffer_ = Manager::instance().getRingBufferPool().getRingBuffer(id_); + ringbuffer_ = Manager::instance().getRingBufferPool().createRingBuffer(streamId_); + Manager::instance().getRingBufferPool().bindHalfDuplexOut(RingBufferPool::DEFAULT_ID, streamId_); if (onSuccessfulSetup_) onSuccessfulSetup_(MEDIA_AUDIO, 1); diff --git a/src/media/audio/audio_receive_thread.h b/src/media/audio/audio_receive_thread.h index ce5688ba0879754dea2b979f3671892eafa84d57..24253ccb6f5653bb6f7e4b39ed896b8429fa1ab4 100644 --- a/src/media/audio/audio_receive_thread.h +++ b/src/media/audio/audio_receive_thread.h @@ -42,7 +42,7 @@ class RingBuffer; class AudioReceiveThread : public Observable<std::shared_ptr<MediaFrame>> { public: - AudioReceiveThread(const std::string& id, + AudioReceiveThread(const std::string& streamId, const AudioFormat& format, const std::string& sdp, const uint16_t mtu); @@ -72,7 +72,7 @@ private: /*-----------------------------------------------------------------*/ /* These variables should be used in thread (i.e. process()) only! */ /*-----------------------------------------------------------------*/ - const std::string id_; + const std::string streamId_; const AudioFormat& format_; DeviceParams args_; diff --git a/src/media/audio/audio_rtp_session.cpp b/src/media/audio/audio_rtp_session.cpp index 2cc92af6719856ab214e1aff568313eca1e317c8..e646527a55eb8366595237bf2ef252add5e14d37 100644 --- a/src/media/audio/audio_rtp_session.cpp +++ b/src/media/audio/audio_rtp_session.cpp @@ -35,6 +35,7 @@ #include "media_decoder.h" #include "media_io_handle.h" #include "media_device.h" +#include "media_const.h" #include "audio/audio_input.h" #include "audio/ringbufferpool.h" @@ -56,29 +57,29 @@ AudioRtpSession::AudioRtpSession(const std::string& callId, { recorder_ = rec; - JAMI_DBG("Created Audio RTP session: %p - call Id %s", this, callId_.c_str()); + JAMI_DEBUG("Created Audio RTP session: {} - stream id {}", fmt::ptr(this), streamId_); // don't move this into the initializer list or Cthulus will emerge - ringbuffer_ = Manager::instance().getRingBufferPool().createRingBuffer(callId_); + ringbuffer_ = Manager::instance().getRingBufferPool().createRingBuffer(streamId_); } AudioRtpSession::~AudioRtpSession() { deinitRecorder(); stop(); - JAMI_DBG("Destroyed Audio RTP session: %p - call Id %s", this, callId_.c_str()); + JAMI_DEBUG("Destroyed Audio RTP session: {} - stream id {}", fmt::ptr(this), streamId_); } void AudioRtpSession::startSender() { std::lock_guard<std::recursive_mutex> lock(mutex_); - JAMI_DBG("Start audio RTP sender: input [%s] - muted [%s]", - input_.c_str(), + JAMI_DEBUG("Start audio RTP sender: input [{}] - muted [{}]", + input_, muteState_ ? "YES" : "NO"); if (not send_.enabled or send_.onHold) { - JAMI_WARN("Audio sending disabled"); + JAMI_WARNING("Audio sending disabled"); if (sender_) { if (socketPair_) socketPair_->interrupt(); @@ -90,12 +91,24 @@ AudioRtpSession::startSender() } if (sender_) - JAMI_WARN("Restarting audio sender"); + JAMI_WARNING("Restarting audio sender"); if (audioInput_) audioInput_->detach(sender_.get()); + bool fileAudio = !input_.empty() && input_.find("file://") != std::string::npos; + auto audioInputId = streamId_; + if (fileAudio) { + auto suffix = input_; + static const std::string& sep = libjami::Media::VideoProtocolPrefix::SEPARATOR; + const auto pos = input_.find(sep); + if (pos != std::string::npos) { + suffix = input_.substr(pos + sep.size()); + } + audioInputId = suffix; + } + // sender sets up input correctly, we just keep a reference in case startSender is called - audioInput_ = jami::getAudioInput(callId_); + audioInput_ = jami::getAudioInput(audioInputId); audioInput_->setRecorderCallback( [w=weak_from_this()](const MediaStream& ms) { Manager::instance().ioContext()->post([w=std::move(w), ms]() { @@ -105,19 +118,23 @@ AudioRtpSession::startSender() }); audioInput_->setMuted(muteState_); audioInput_->setSuccessfulSetupCb(onSuccessfulSetup_); - auto newParams = audioInput_->switchInput(input_); - try { - if (newParams.valid() - && newParams.wait_for(NEWPARAMS_TIMEOUT) == std::future_status::ready) { - localAudioParams_ = newParams.get(); - } else { - JAMI_ERR() << "No valid new audio parameters"; + if (!fileAudio) { + auto newParams = audioInput_->switchInput(input_); + try { + if (newParams.valid() + && newParams.wait_for(NEWPARAMS_TIMEOUT) == std::future_status::ready) { + localAudioParams_ = newParams.get(); + } else { + JAMI_ERROR("No valid new audio parameters"); + return; + } + } catch (const std::exception& e) { + JAMI_ERROR("Exception while retrieving audio parameters: {}", e.what()); return; } - } catch (const std::exception& e) { - JAMI_ERR() << "Exception while retrieving audio parameters: " << e.what(); - return; } + if (streamId_ != audioInput_->getId()) + Manager::instance().getRingBufferPool().bindHalfDuplexOut(streamId_, audioInput_->getId()); send_.fecEnabled = true; @@ -130,19 +147,17 @@ AudioRtpSession::startSender() socketPair_->stopSendOp(false); sender_.reset(new AudioSender(getRemoteRtpUri(), send_, *socketPair_, initSeqVal_, mtu_)); } catch (const MediaEncoderException& e) { - JAMI_ERR("%s", e.what()); + JAMI_ERROR("{}", e.what()); send_.enabled = false; } - if (voiceCallback_) { + if (voiceCallback_) sender_->setVoiceCallback(voiceCallback_); - } // NOTE do after sender/encoder are ready auto codec = std::static_pointer_cast<SystemAudioCodecInfo>(send_.codec); audioInput_->setFormat(codec->audioformat); - if (audioInput_) - audioInput_->attach(sender_.get()); + audioInput_->attach(sender_.get()); if (not rtcpCheckerThread_.isRunning()) rtcpCheckerThread_.start(); @@ -167,16 +182,16 @@ AudioRtpSession::startReceiver() socketPair_->setReadBlockingMode(true); if (not receive_.enabled or receive_.onHold) { - JAMI_WARN("Audio receiving disabled"); + JAMI_WARNING("Audio receiving disabled"); receiveThread_.reset(); return; } if (receiveThread_) - JAMI_WARN("Restarting audio receiver"); + JAMI_WARNING("Restarting audio receiver"); auto accountAudioCodec = std::static_pointer_cast<SystemAudioCodecInfo>(receive_.codec); - receiveThread_.reset(new AudioReceiveThread(callId_, + receiveThread_.reset(new AudioReceiveThread(streamId_, accountAudioCodec->audioformat, receive_.receiving_sdp, mtu_)); @@ -225,7 +240,7 @@ AudioRtpSession::start(std::unique_ptr<dhtnet::IceSocket> rtp_sock, std::unique_ send_.crypto.getSrtpKeyInfo().c_str()); } } catch (const std::runtime_error& e) { - JAMI_ERR("Socket creation failed: %s", e.what()); + JAMI_ERROR("Socket creation failed: {}", e.what()); return; } @@ -238,7 +253,7 @@ AudioRtpSession::stop() { std::lock_guard<std::recursive_mutex> lock(mutex_); - JAMI_DBG("[%p] Stopping receiver", this); + JAMI_DEBUG("[{}] Stopping receiver", fmt::ptr(this)); if (not receiveThread_) return; @@ -276,6 +291,7 @@ AudioRtpSession::setMuted(bool muted, Direction dir) } else { if (shared->receiveThread_) { auto ms = shared->receiveThread_->getInfo(); + ms.name = shared->streamId_ + ":remote"; if (muted) { if (auto ob = shared->recorder_->getStream(ms.name)) { shared->receiveThread_->detach(ob); @@ -354,9 +370,9 @@ AudioRtpSession::setNewPacketLoss(unsigned int newPL) auto ret = sender_->setPacketLoss(newPL); packetLoss_ = newPL; if (ret == -1) - JAMI_ERR("Fail to access the encoder"); + JAMI_ERROR("Fail to access the encoder"); } else { - JAMI_ERR("Fail to access the sender"); + JAMI_ERROR("Fail to access the sender"); } } } @@ -387,7 +403,9 @@ AudioRtpSession::attachRemoteRecorder(const MediaStream& ms) std::lock_guard<std::recursive_mutex> lock(mutex_); if (!recorder_ || !receiveThread_) return; - if (auto ob = recorder_->addStream(ms)) { + MediaStream remoteMS = ms; + remoteMS.name = streamId_ + ":remote"; + if (auto ob = recorder_->addStream(remoteMS)) { receiveThread_->attach(ob); } } @@ -398,7 +416,9 @@ AudioRtpSession::attachLocalRecorder(const MediaStream& ms) std::lock_guard<std::recursive_mutex> lock(mutex_); if (!recorder_ || !audioInput_) return; - if (auto ob = recorder_->addStream(ms)) { + MediaStream localMS = ms; + localMS.name = streamId_ + ":local"; + if (auto ob = recorder_->addStream(localMS)) { audioInput_->attach(ob); } } @@ -433,6 +453,7 @@ AudioRtpSession::deinitRecorder() return; if (receiveThread_) { auto ms = receiveThread_->getInfo(); + ms.name = streamId_ + ":remote"; if (auto ob = recorder_->getStream(ms.name)) { receiveThread_->detach(ob); recorder_->removeStream(ms); @@ -440,6 +461,7 @@ AudioRtpSession::deinitRecorder() } if (audioInput_) { auto ms = audioInput_->getInfo(); + ms.name = streamId_ + ":local"; if (auto ob = recorder_->getStream(ms.name)) { audioInput_->detach(ob); recorder_->removeStream(ms); diff --git a/src/media/audio/ringbuffer.cpp b/src/media/audio/ringbuffer.cpp index 6e683f5f67441c8c8b2302db22f0d9baef3a1676..7393bc44c52f16c2c123d6dcf57c1aa3ffba409d 100644 --- a/src/media/audio/ringbuffer.cpp +++ b/src/media/audio/ringbuffer.cpp @@ -52,18 +52,18 @@ RingBuffer::RingBuffer(const std::string& rbuf_id, size_t /*size*/, AudioFormat putToBuffer(std::move(frame)); }) { - JAMI_INFO("Create new RingBuffer %s", id.c_str()); + JAMI_LOG("Create new RingBuffer {}", id); } RingBuffer::~RingBuffer() { - JAMI_INFO("Destroy RingBuffer %s", id.c_str()); + JAMI_LOG("Destroy RingBuffer {}", id); } void -RingBuffer::flush(const std::string& call_id) +RingBuffer::flush(const std::string& ringbufferId) { - storeReadOffset(endPos_, call_id); + storeReadOffset(endPos_, ringbufferId); } void @@ -84,12 +84,12 @@ RingBuffer::putLength() const } size_t -RingBuffer::getLength(const std::string& call_id) const +RingBuffer::getLength(const std::string& ringbufferId) const { const size_t buffer_size = buffer_.size(); if (buffer_size == 0) return 0; - return (endPos_ + buffer_size - getReadOffset(call_id)) % buffer_size; + return (endPos_ + buffer_size - getReadOffset(ringbufferId)) % buffer_size; } void @@ -99,9 +99,9 @@ RingBuffer::debug() } size_t -RingBuffer::getReadOffset(const std::string& call_id) const +RingBuffer::getReadOffset(const std::string& ringbufferId) const { - auto iter = readoffsets_.find(call_id); + auto iter = readoffsets_.find(ringbufferId); return (iter != readoffsets_.end()) ? iter->second.offset : 0; } @@ -117,37 +117,37 @@ RingBuffer::getSmallestReadOffset() const } void -RingBuffer::storeReadOffset(size_t offset, const std::string& call_id) +RingBuffer::storeReadOffset(size_t offset, const std::string& ringbufferId) { - ReadOffsetMap::iterator iter = readoffsets_.find(call_id); + ReadOffsetMap::iterator iter = readoffsets_.find(ringbufferId); if (iter != readoffsets_.end()) iter->second.offset = offset; else - JAMI_ERR("RingBuffer::storeReadOffset() failed: unknown call '%s'", call_id.c_str()); + JAMI_ERROR("RingBuffer::storeReadOffset() failed: unknown ringbuffer '{}'", ringbufferId); } void -RingBuffer::createReadOffset(const std::string& call_id) +RingBuffer::createReadOffset(const std::string& ringbufferId) { std::lock_guard<std::mutex> l(lock_); - if (!hasThisReadOffset(call_id)) - readoffsets_.emplace(call_id, ReadOffset {endPos_, {}}); + if (!hasThisReadOffset(ringbufferId)) + readoffsets_.emplace(ringbufferId, ReadOffset {endPos_, {}}); } void -RingBuffer::removeReadOffset(const std::string& call_id) +RingBuffer::removeReadOffset(const std::string& ringbufferId) { std::lock_guard<std::mutex> l(lock_); - auto iter = readoffsets_.find(call_id); + auto iter = readoffsets_.find(ringbufferId); if (iter != readoffsets_.end()) readoffsets_.erase(iter); } bool -RingBuffer::hasThisReadOffset(const std::string& call_id) const +RingBuffer::hasThisReadOffset(const std::string& ringbufferId) const { - return readoffsets_.find(call_id) != readoffsets_.end(); + return readoffsets_.find(ringbufferId) != readoffsets_.end(); } bool @@ -211,18 +211,18 @@ RingBuffer::putToBuffer(std::shared_ptr<AudioFrame>&& data) // size_t -RingBuffer::availableForGet(const std::string& call_id) const +RingBuffer::availableForGet(const std::string& ringbufferId) const { // Used space - return getLength(call_id); + return getLength(ringbufferId); } std::shared_ptr<AudioFrame> -RingBuffer::get(const std::string& call_id) +RingBuffer::get(const std::string& ringbufferId) { std::lock_guard<std::mutex> l(lock_); - auto offset = readoffsets_.find(call_id); + auto offset = readoffsets_.find(ringbufferId); if (offset == readoffsets_.end()) return {}; @@ -241,20 +241,20 @@ RingBuffer::get(const std::string& call_id) } size_t -RingBuffer::waitForDataAvailable(const std::string& call_id, const time_point& deadline) const +RingBuffer::waitForDataAvailable(const std::string& ringbufferId, const time_point& deadline) const { std::unique_lock<std::mutex> l(lock_); if (buffer_.empty()) return 0; - if (readoffsets_.find(call_id) == readoffsets_.end()) + if (readoffsets_.find(ringbufferId) == readoffsets_.end()) return 0; size_t getl = 0; auto check = [=, &getl] { // Re-find read_ptr: it may be destroyed during the wait const size_t buffer_size = buffer_.size(); - const auto read_ptr = readoffsets_.find(call_id); + const auto read_ptr = readoffsets_.find(ringbufferId); if (buffer_size == 0 || read_ptr == readoffsets_.end()) return true; getl = (endPos_ + buffer_size - read_ptr->second.offset) % buffer_size; @@ -272,7 +272,7 @@ RingBuffer::waitForDataAvailable(const std::string& call_id, const time_point& d } size_t -RingBuffer::discard(size_t toDiscard, const std::string& call_id) +RingBuffer::discard(size_t toDiscard, const std::string& ringbufferId) { std::lock_guard<std::mutex> l(lock_); @@ -280,7 +280,7 @@ RingBuffer::discard(size_t toDiscard, const std::string& call_id) if (buffer_size == 0) return 0; - auto offset = readoffsets_.find(call_id); + auto offset = readoffsets_.find(ringbufferId); if (offset == readoffsets_.end()) return 0; diff --git a/src/media/audio/ringbuffer.h b/src/media/audio/ringbuffer.h index 582e4c8bfcc561289b26dc8c935851c839382543..dd965a724cd4ab0d12e98bdf227a3ef39d4278c4 100644 --- a/src/media/audio/ringbuffer.h +++ b/src/media/audio/ringbuffer.h @@ -64,7 +64,7 @@ public: /** * Reset the counters to 0 for this read offset */ - void flush(const std::string& call_id); + void flush(const std::string& ringbufferId); void flushAll(); @@ -80,21 +80,20 @@ public: /** * Add a new readoffset for this ringbuffer */ - void createReadOffset(const std::string& call_id); + void createReadOffset(const std::string& ringbufferId); - void createReadOffset(const std::string& call_id, FrameCallback cb); + void createReadOffset(const std::string& ringbufferId, FrameCallback cb); /** * Remove a readoffset for this ringbuffer */ - void removeReadOffset(const std::string& call_id); + void removeReadOffset(const std::string& ringbufferId); size_t readOffsetCount() const { return readoffsets_.size(); } /** * Write data in the ring buffer - * @param buffer Data to copied - * @param toCopy Number of bytes to copy + * @param AudioFrame */ void put(std::shared_ptr<AudioFrame>&& data); @@ -102,22 +101,21 @@ public: * To get how much samples are available in the buffer to read in * @return int The available (multichannel) samples number */ - size_t availableForGet(const std::string& call_id) const; + size_t availableForGet(const std::string& ringbufferId) const; /** * Get data in the ring buffer - * @param buffer Data to copied - * @param toCopy Number of bytes to copy - * @return size_t Number of bytes copied + * @param ringbufferId + * @return AudioFRame */ - std::shared_ptr<AudioFrame> get(const std::string& call_id); + std::shared_ptr<AudioFrame> get(const std::string& ringbufferId); /** * Discard data from the buffer * @param toDiscard Number of samples to discard * @return size_t Number of samples discarded */ - size_t discard(size_t toDiscard, const std::string& call_id); + size_t discard(size_t toDiscard, const std::string& ringbufferId); /** * Total length of the ring buffer which is available for "putting" @@ -125,7 +123,7 @@ public: */ size_t putLength() const; - size_t getLength(const std::string& call_id) const; + size_t getLength(const std::string& ringbufferId) const; inline bool isFull() const { return putLength() == buffer_.size(); } @@ -136,13 +134,13 @@ public: /** * Blocks until min_data_length samples of data is available, or until deadline has passed. * - * @param call_id The read offset for which data should be available. + * @param ringbufferId The read offset for which data should be available. * @param min_data_length Minimum number of samples that should be available for the call to return - * @param deadline The call is guaranteed to end after this time point. If no deadline is provided, + * @param deadline The ringbufferId is guaranteed to end after this time point. If no deadline is provided, * the call blocks indefinitely. - * @return available data for call_id after the call returned (same as calling getLength(call_id) ). + * @return available data for ringbufferId after the call returned (same as calling getLength(ringbufferId) ). */ - size_t waitForDataAvailable(const std::string& call_id, + size_t waitForDataAvailable(const std::string& ringbufferId, const time_point& deadline = time_point::max()) const; /** @@ -174,17 +172,17 @@ private: /** * Get read offset coresponding to this call */ - size_t getReadOffset(const std::string& call_id) const; + size_t getReadOffset(const std::string& ringbufferId) const; /** * Move readoffset forward by offset */ - void storeReadOffset(size_t offset, const std::string& call_id); + void storeReadOffset(size_t offset, const std::string& ringbufferId); /** * Test if readoffset coresponding to this call is still active */ - bool hasThisReadOffset(const std::string& call_id) const; + bool hasThisReadOffset(const std::string& ringbufferId) const; /** * Discard data from all read offsets to make place for new data. diff --git a/src/media/audio/ringbufferpool.cpp b/src/media/audio/ringbufferpool.cpp index 674822f6880c485f3c67fcca1ea77b9c20d7228f..1cce8518057f7af38f69e997791cbd93d4aadb24 100644 --- a/src/media/audio/ringbufferpool.cpp +++ b/src/media/audio/ringbufferpool.cpp @@ -47,7 +47,7 @@ RingBufferPool::~RingBufferPool() for (const auto& item : ringBufferMap_) { const auto& weak = item.second; if (not weak.expired()) - JAMI_WARN("Leaking RingBuffer '%s'", item.first.c_str()); + JAMI_WARNING("Leaking RingBuffer '{}'", item.first); } } @@ -110,7 +110,7 @@ RingBufferPool::createRingBuffer(const std::string& id) auto rbuf = getRingBuffer(id); if (rbuf) { - JAMI_DBG("Ringbuffer already exists for id '%s'", id.c_str()); + JAMI_DEBUG("Ringbuffer already exists for id '{}'", id); return rbuf; } @@ -120,183 +120,183 @@ RingBufferPool::createRingBuffer(const std::string& id) } const RingBufferPool::ReadBindings* -RingBufferPool::getReadBindings(const std::string& call_id) const +RingBufferPool::getReadBindings(const std::string& ringbufferId) const { - const auto& iter = readBindingsMap_.find(call_id); + const auto& iter = readBindingsMap_.find(ringbufferId); return iter != readBindingsMap_.cend() ? &iter->second : nullptr; } RingBufferPool::ReadBindings* -RingBufferPool::getReadBindings(const std::string& call_id) +RingBufferPool::getReadBindings(const std::string& ringbufferId) { - const auto& iter = readBindingsMap_.find(call_id); + const auto& iter = readBindingsMap_.find(ringbufferId); return iter != readBindingsMap_.cend() ? &iter->second : nullptr; } void -RingBufferPool::removeReadBindings(const std::string& call_id) +RingBufferPool::removeReadBindings(const std::string& ringbufferId) { - if (not readBindingsMap_.erase(call_id)) - JAMI_ERR("CallID set %s does not exist!", call_id.c_str()); + if (not readBindingsMap_.erase(ringbufferId)) + JAMI_ERROR("Ringbuffer {} does not exist!", ringbufferId); } /** - * Make given call ID a reader of given ring buffer + * Make given ringbuffer a reader of given ring buffer */ void RingBufferPool::addReaderToRingBuffer(const std::shared_ptr<RingBuffer>& rbuf, - const std::string& call_id) + const std::string& ringbufferId) { - if (call_id != DEFAULT_ID and rbuf->getId() == call_id) - JAMI_WARN("RingBuffer has a readoffset on itself"); + if (ringbufferId != DEFAULT_ID and rbuf->getId() == ringbufferId) + JAMI_WARNING("RingBuffer has a readoffset on itself"); - rbuf->createReadOffset(call_id); - readBindingsMap_[call_id].insert(rbuf); // bindings list created if not existing - JAMI_DBG("Bind rbuf '%s' to callid '%s'", rbuf->getId().c_str(), call_id.c_str()); + rbuf->createReadOffset(ringbufferId); + readBindingsMap_[ringbufferId].insert(rbuf); // bindings list created if not existing + JAMI_DEBUG("Bind rbuf '{}' to ringbuffer '{}'", rbuf->getId(), ringbufferId); } void RingBufferPool::removeReaderFromRingBuffer(const std::shared_ptr<RingBuffer>& rbuf, - const std::string& call_id) + const std::string& ringbufferId) { - if (auto bindings = getReadBindings(call_id)) { + if (auto bindings = getReadBindings(ringbufferId)) { bindings->erase(rbuf); if (bindings->empty()) - removeReadBindings(call_id); + removeReadBindings(ringbufferId); } - rbuf->removeReadOffset(call_id); + rbuf->removeReadOffset(ringbufferId); } void -RingBufferPool::bindCallID(const std::string& call_id1, const std::string& call_id2) +RingBufferPool::bindRingbuffers(const std::string& ringbufferId1, const std::string& ringbufferId2) { - JAMI_INFO("Bind call %s to call %s", call_id1.c_str(), call_id2.c_str()); + JAMI_LOG("Bind ringbuffer {} to ringbuffer {}", ringbufferId1, ringbufferId2); - const auto& rb_call1 = getRingBuffer(call_id1); - if (not rb_call1) { - JAMI_ERR("No ringbuffer associated with call '%s'", call_id1.c_str()); + const auto& rb1 = getRingBuffer(ringbufferId1); + if (not rb1) { + JAMI_ERROR("No ringbuffer associated with id '{}'", ringbufferId1); return; } - const auto& rb_call2 = getRingBuffer(call_id2); - if (not rb_call2) { - JAMI_ERR("No ringbuffer associated to call '%s'", call_id2.c_str()); + const auto& rb2 = getRingBuffer(ringbufferId2); + if (not rb2) { + JAMI_ERROR("No ringbuffer associated to id '{}'", ringbufferId2); return; } std::lock_guard<std::recursive_mutex> lk(stateLock_); - addReaderToRingBuffer(rb_call1, call_id2); - addReaderToRingBuffer(rb_call2, call_id1); + addReaderToRingBuffer(rb1, ringbufferId2); + addReaderToRingBuffer(rb2, ringbufferId1); } void -RingBufferPool::bindHalfDuplexOut(const std::string& process_id, const std::string& call_id) +RingBufferPool::bindHalfDuplexOut(const std::string& processId, const std::string& ringbufferId) { - /* This method is used only for active calls, if this call does not exist, + /* This method is used only for active ringbuffers, if this ringbuffer does not exist, * do nothing */ - if (const auto& rb = getRingBuffer(call_id)) { + if (const auto& rb = getRingBuffer(ringbufferId)) { std::lock_guard<std::recursive_mutex> lk(stateLock_); - addReaderToRingBuffer(rb, process_id); + addReaderToRingBuffer(rb, processId); } } void -RingBufferPool::unBindCallID(const std::string& call_id1, const std::string& call_id2) +RingBufferPool::unbindRingbuffers(const std::string& ringbufferId1, const std::string& ringbufferId2) { - JAMI_INFO("Unbind calls %s and %s", call_id1.c_str(), call_id2.c_str()); + JAMI_LOG("Unbind ringbuffers {} and {}", ringbufferId1, ringbufferId2); - const auto& rb_call1 = getRingBuffer(call_id1); - if (not rb_call1) { - JAMI_ERR("No ringbuffer associated to call '%s'", call_id1.c_str()); + const auto& rb1 = getRingBuffer(ringbufferId1); + if (not rb1) { + JAMI_ERROR("No ringbuffer associated to id '{}'", ringbufferId1); return; } - const auto& rb_call2 = getRingBuffer(call_id2); - if (not rb_call2) { - JAMI_ERR("No ringbuffer associated to call '%s'", call_id2.c_str()); + const auto& rb2 = getRingBuffer(ringbufferId2); + if (not rb2) { + JAMI_ERROR("No ringbuffer associated to id '{}'", ringbufferId2); return; } std::lock_guard<std::recursive_mutex> lk(stateLock_); - removeReaderFromRingBuffer(rb_call1, call_id2); - removeReaderFromRingBuffer(rb_call2, call_id1); + removeReaderFromRingBuffer(rb1, ringbufferId2); + removeReaderFromRingBuffer(rb2, ringbufferId1); } void -RingBufferPool::unBindHalfDuplexOut(const std::string& process_id, const std::string& call_id) +RingBufferPool::unBindHalfDuplexOut(const std::string& process_id, const std::string& ringbufferId) { std::lock_guard<std::recursive_mutex> lk(stateLock_); - if (const auto& rb = getRingBuffer(call_id)) + if (const auto& rb = getRingBuffer(ringbufferId)) removeReaderFromRingBuffer(rb, process_id); } void -RingBufferPool::unBindAllHalfDuplexOut(const std::string& call_id) +RingBufferPool::unBindAllHalfDuplexOut(const std::string& ringbufferId) { - const auto& rb_call = getRingBuffer(call_id); - if (not rb_call) { - JAMI_ERR("No ringbuffer associated to call '%s'", call_id.c_str()); + const auto& rb = getRingBuffer(ringbufferId); + if (not rb) { + JAMI_ERROR("No ringbuffer associated to id '{}'", ringbufferId); return; } std::lock_guard<std::recursive_mutex> lk(stateLock_); - auto bindings = getReadBindings(call_id); + auto bindings = getReadBindings(ringbufferId); if (not bindings) return; const auto bindings_copy = *bindings; // temporary copy for (const auto& rbuf : bindings_copy) { - removeReaderFromRingBuffer(rb_call, rbuf->getId()); + removeReaderFromRingBuffer(rb, rbuf->getId()); } } void -RingBufferPool::unBindAll(const std::string& call_id) +RingBufferPool::unBindAll(const std::string& ringbufferId) { - JAMI_INFO("Unbind call %s from all bound calls", call_id.c_str()); + JAMI_LOG("Unbind ringbuffer {} from all bound ringbuffers", ringbufferId); - const auto& rb_call = getRingBuffer(call_id); - if (not rb_call) { - JAMI_ERR("No ringbuffer associated to call '%s'", call_id.c_str()); + const auto& rb = getRingBuffer(ringbufferId); + if (not rb) { + JAMI_ERROR("No ringbuffer associated to id '{}'", ringbufferId); return; } std::lock_guard<std::recursive_mutex> lk(stateLock_); - auto bindings = getReadBindings(call_id); + auto bindings = getReadBindings(ringbufferId); if (not bindings) return; const auto bindings_copy = *bindings; // temporary copy for (const auto& rbuf : bindings_copy) { - removeReaderFromRingBuffer(rbuf, call_id); - removeReaderFromRingBuffer(rb_call, rbuf->getId()); + removeReaderFromRingBuffer(rbuf, ringbufferId); + removeReaderFromRingBuffer(rb, rbuf->getId()); } } std::shared_ptr<AudioFrame> -RingBufferPool::getData(const std::string& call_id) +RingBufferPool::getData(const std::string& ringbufferId) { std::lock_guard<std::recursive_mutex> lk(stateLock_); - const auto bindings = getReadBindings(call_id); + const auto bindings = getReadBindings(ringbufferId); if (not bindings) return {}; // No mixing if (bindings->size() == 1) - return (*bindings->cbegin())->get(call_id); + return (*bindings->cbegin())->get(ringbufferId); auto mixBuffer = std::make_shared<AudioFrame>(internalAudioFormat_); auto mixed = false; for (const auto& rbuf : *bindings) { - if (auto b = rbuf->get(call_id)) { + if (auto b = rbuf->get(ringbufferId)) { mixed = true; mixBuffer->mix(*b); @@ -309,7 +309,7 @@ RingBufferPool::getData(const std::string& call_id) } bool -RingBufferPool::waitForDataAvailable(const std::string& call_id, +RingBufferPool::waitForDataAvailable(const std::string& ringbufferId, const std::chrono::microseconds& max_wait) const { std::unique_lock<std::recursive_mutex> lk(stateLock_); @@ -317,14 +317,14 @@ RingBufferPool::waitForDataAvailable(const std::string& call_id, // convert to absolute time const auto deadline = std::chrono::high_resolution_clock::now() + max_wait; - auto bindings = getReadBindings(call_id); + auto bindings = getReadBindings(ringbufferId); if (not bindings) return 0; const auto bindings_copy = *bindings; // temporary copy for (const auto& rbuf : bindings_copy) { lk.unlock(); - if (rbuf->waitForDataAvailable(call_id, deadline) == 0) + if (rbuf->waitForDataAvailable(ringbufferId, deadline) == 0) return false; lk.lock(); } @@ -332,30 +332,30 @@ RingBufferPool::waitForDataAvailable(const std::string& call_id, } std::shared_ptr<AudioFrame> -RingBufferPool::getAvailableData(const std::string& call_id) +RingBufferPool::getAvailableData(const std::string& ringbufferId) { std::lock_guard<std::recursive_mutex> lk(stateLock_); - auto bindings = getReadBindings(call_id); + auto bindings = getReadBindings(ringbufferId); if (not bindings) return 0; // No mixing if (bindings->size() == 1) { - return (*bindings->cbegin())->get(call_id); + return (*bindings->cbegin())->get(ringbufferId); } size_t availableFrames = 0; for (const auto& rbuf : *bindings) - availableFrames = std::min(availableFrames, rbuf->availableForGet(call_id)); + availableFrames = std::min(availableFrames, rbuf->availableForGet(ringbufferId)); if (availableFrames == 0) return {}; auto buf = std::make_shared<AudioFrame>(internalAudioFormat_); for (const auto& rbuf : *bindings) { - if (auto b = rbuf->get(call_id)) { + if (auto b = rbuf->get(ringbufferId)) { buf->mix(*b); // voice is true if any of mixed frames has voice @@ -367,23 +367,23 @@ RingBufferPool::getAvailableData(const std::string& call_id) } size_t -RingBufferPool::availableForGet(const std::string& call_id) const +RingBufferPool::availableForGet(const std::string& ringbufferId) const { std::lock_guard<std::recursive_mutex> lk(stateLock_); - const auto bindings = getReadBindings(call_id); + const auto bindings = getReadBindings(ringbufferId); if (not bindings) return 0; // No mixing if (bindings->size() == 1) { - return (*bindings->begin())->availableForGet(call_id); + return (*bindings->begin())->availableForGet(ringbufferId); } size_t availableSamples = std::numeric_limits<size_t>::max(); for (const auto& rbuf : *bindings) { - const size_t nbSamples = rbuf->availableForGet(call_id); + const size_t nbSamples = rbuf->availableForGet(ringbufferId); if (nbSamples != 0) availableSamples = std::min(availableSamples, nbSamples); } @@ -392,31 +392,31 @@ RingBufferPool::availableForGet(const std::string& call_id) const } size_t -RingBufferPool::discard(size_t toDiscard, const std::string& call_id) +RingBufferPool::discard(size_t toDiscard, const std::string& ringbufferId) { std::lock_guard<std::recursive_mutex> lk(stateLock_); - const auto bindings = getReadBindings(call_id); + const auto bindings = getReadBindings(ringbufferId); if (not bindings) return 0; for (const auto& rbuf : *bindings) - rbuf->discard(toDiscard, call_id); + rbuf->discard(toDiscard, ringbufferId); return toDiscard; } void -RingBufferPool::flush(const std::string& call_id) +RingBufferPool::flush(const std::string& ringbufferId) { std::lock_guard<std::recursive_mutex> lk(stateLock_); - const auto bindings = getReadBindings(call_id); + const auto bindings = getReadBindings(ringbufferId); if (not bindings) return; for (const auto& rbuf : *bindings) - rbuf->flush(call_id); + rbuf->flush(ringbufferId); } void diff --git a/src/media/audio/ringbufferpool.h b/src/media/audio/ringbufferpool.h index 76c64554ff468c335a3411f9a7036c16e3d75929..9b6f6255cb93ccfd3fdf2dcba42e893d2985b253 100644 --- a/src/media/audio/ringbufferpool.h +++ b/src/media/audio/ringbufferpool.h @@ -52,44 +52,43 @@ public: void setInternalAudioFormat(AudioFormat format); /** - * Bind together two audio streams so that a client will be able - * to put and get data specifying its callid only. + * Bind together two audio streams */ - void bindCallID(const std::string& call_id1, const std::string& call_id2); + void bindRingbuffers(const std::string& ringbufferId1, const std::string& ringbufferId2); /** - * Add a new call_id to unidirectional outgoing stream - * \param call_id New call id to be added for this stream - * \param process_id Process that require this stream + * Add a new ringbufferId to unidirectional outgoing stream + * \param ringbufferId New ringbufferId to be added for this stream + * \param processId Process that require this stream */ - void bindHalfDuplexOut(const std::string& process_id, const std::string& call_id); + void bindHalfDuplexOut(const std::string& processId, const std::string& ringbufferId); /** - * Unbind two calls + * Unbind two ringbuffers */ - void unBindCallID(const std::string& call_id1, const std::string& call_id2); + void unbindRingbuffers(const std::string& ringbufferId1, const std::string& ringbufferId2); /** * Unbind a unidirectional stream */ - void unBindHalfDuplexOut(const std::string& process_id, const std::string& call_id); + void unBindHalfDuplexOut(const std::string& process_id, const std::string& ringbufferId); - void unBindAllHalfDuplexOut(const std::string& call_id); + void unBindAllHalfDuplexOut(const std::string& ringbufferId); - void unBindAll(const std::string& call_id); + void unBindAll(const std::string& ringbufferId); - bool waitForDataAvailable(const std::string& call_id, + bool waitForDataAvailable(const std::string& ringbufferId, const std::chrono::microseconds& max_wait) const; - std::shared_ptr<AudioFrame> getData(const std::string& call_id); + std::shared_ptr<AudioFrame> getData(const std::string& ringbufferId); - std::shared_ptr<AudioFrame> getAvailableData(const std::string& call_id); + std::shared_ptr<AudioFrame> getAvailableData(const std::string& ringbufferId); - size_t availableForGet(const std::string& call_id) const; + size_t availableForGet(const std::string& ringbufferId) const; - size_t discard(size_t toDiscard, const std::string& call_id); + size_t discard(size_t toDiscard, const std::string& ringbufferId); - void flush(const std::string& call_id); + void flush(const std::string& ringbufferId); void flushAllBuffers(); @@ -123,15 +122,15 @@ private: using ReadBindings = std::set<std::shared_ptr<RingBuffer>, std::owner_less<std::shared_ptr<RingBuffer>>>; - const ReadBindings* getReadBindings(const std::string& call_id) const; - ReadBindings* getReadBindings(const std::string& call_id); + const ReadBindings* getReadBindings(const std::string& ringbufferId) const; + ReadBindings* getReadBindings(const std::string& ringbufferId); - void removeReadBindings(const std::string& call_id); + void removeReadBindings(const std::string& ringbufferId); - void addReaderToRingBuffer(const std::shared_ptr<RingBuffer>& rbuf, const std::string& call_id); + void addReaderToRingBuffer(const std::shared_ptr<RingBuffer>& rbuf, const std::string& ringbufferId); void removeReaderFromRingBuffer(const std::shared_ptr<RingBuffer>& rbuf, - const std::string& call_id); + const std::string& ringbufferId); // A cache of created RingBuffers listed by IDs. std::map<std::string, std::weak_ptr<RingBuffer>> ringBufferMap_ {}; diff --git a/src/media/media_player.cpp b/src/media/media_player.cpp index fd0c971886939cf3c2d82d3bd0fec3a4b139a624..271f334a71ad9ee91d3affc0a06c93d633ebf71f 100644 --- a/src/media/media_player.cpp +++ b/src/media/media_player.cpp @@ -42,11 +42,6 @@ MediaPlayer::MediaPlayer(const std::string& resource) path_ = suffix; - if (access(suffix.c_str(), R_OK) != 0) { - JAMI_ERR() << "File '" << path_ << "' not available"; - return; - } - audioInput_ = jami::getAudioInput(path_); audioInput_->setPaused(paused_); #ifdef ENABLE_VIDEO diff --git a/src/media/media_recorder.cpp b/src/media/media_recorder.cpp index 69a10ba967f3903ccb618a46520f4c478b4fc44a..3273211a2c089eda30f1c74e0f6c5f3913573a4a 100644 --- a/src/media/media_recorder.cpp +++ b/src/media/media_recorder.cpp @@ -609,60 +609,25 @@ MediaRecorder::buildVideoFilter(const std::vector<MediaStream>& peers, void MediaRecorder::setupAudioOutput() { - 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()) - 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; - - 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; + MediaStream encoderStream; // 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() + mixer.isValid(); - switch (streams) { - case 0: { + + if (streams_.empty()) { JAMI_WARN() << "Trying to record a audio stream but none is valid"; return; } - case 1: { - 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; - } - case 2: // mix both audio streams - ret = audioFilter_->initialize(buildAudioFilter({peer}, local), {peer, local}); - break; - default: - JAMI_ERR() << "Recording more than 2 audio streams is not supported"; - break; + + std::vector<MediaStream> peers {}; + for (const auto& media : streams_) { + if (!media.second->info.isVideo && media.second->info.isValid()) + peers.emplace_back(media.second->info); } + ret = audioFilter_->initialize(buildAudioFilter(peers), peers); + if (ret < 0) { JAMI_ERR() << "Failed to initialize audio filter"; return; @@ -679,9 +644,9 @@ MediaRecorder::setupAudioOutput() } outputAudioFilter_.reset(new MediaFilter); - ret = outputAudioFilter_ - ->initialize("[input]aformat=sample_fmts=s16:sample_rates=48000:channel_layouts=stereo", - {secondaryFilter}); + ret = outputAudioFilter_->initialize( + "[input]aformat=sample_fmts=s16:sample_rates=48000:channel_layouts=stereo", + {secondaryFilter}); if (ret < 0) { JAMI_ERR() << "Failed to initialize output audio filter"; @@ -691,24 +656,14 @@ MediaRecorder::setupAudioOutput() } std::string -MediaRecorder::buildAudioFilter(const std::vector<MediaStream>& peers, - const MediaStream& local) const +MediaRecorder::buildAudioFilter(const std::vector<MediaStream>& peers) const { std::string baseFilter = "aresample=osr=48000:ochl=stereo:osf=s16"; std::ostringstream a; - switch (peers.size()) { - case 0: - a << "[" << local.name << "] " << baseFilter; - break; - default: - a << "[" << local.name << "] "; - for (const auto& ms : peers) - a << "[" << ms.name << "] "; - a << " amix=inputs=" << peers.size() + (local.isValid() ? 1 : 0) << ", " << baseFilter; - break; - } - + for (const auto& ms : peers) + a << "[" << ms.name << "] "; + a << " amix=inputs=" << peers.size() << ", " << baseFilter; return a.str(); } diff --git a/src/media/media_recorder.h b/src/media/media_recorder.h index ef1773756170a2efbf524199796fd3a907e0366a..59813787930aae0533fc98051f7361773c4dfe10 100644 --- a/src/media/media_recorder.h +++ b/src/media/media_recorder.h @@ -140,8 +140,7 @@ private: const MediaStream& local) const; void setupAudioOutput(); std::mutex mutexStreamSetup_; - std::string buildAudioFilter(const std::vector<MediaStream>& peers, - const MediaStream& local) const; + std::string buildAudioFilter(const std::vector<MediaStream>& peers) const; std::mutex mutexFrameBuff_; std::mutex mutexFilterVideo_; diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp index 05658b9143c4f425f1abd25415cb5774e8fec923..cbe08582ff2194159794095b75a8249a1844d808 100644 --- a/src/sip/sipcall.cpp +++ b/src/sip/sipcall.cpp @@ -105,6 +105,9 @@ static const std::vector<unsigned> NEW_CONFPROTOCOL_VERSION static constexpr auto REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR = "11.0.2"sv; static const std::vector<unsigned> REUSE_ICE_IN_REINVITE_REQUIRED_VERSION = split_string_to_unsigned(REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR, '.'); +static constexpr auto MULTIAUDIO_REQUIRED_VERSION_STR = "13.11.0"sv; +static const std::vector<unsigned> MULTIAUDIO_REQUIRED_VERSION + = split_string_to_unsigned(MULTIAUDIO_REQUIRED_VERSION_STR, '.'); SIPCall::SIPCall(const std::shared_ptr<SIPAccountBase>& account, const std::string& callId, @@ -1547,7 +1550,6 @@ SIPCall::setVideoOrientation(int streamIdx, int rotation) void SIPCall::sendTextMessage(const std::map<std::string, std::string>& messages, const std::string& from) { - std::lock_guard<std::recursive_mutex> lk {callMutex_}; // TODO: for now we ignore the "from" (the previous implementation for sending this info was // buggy and verbose), another way to send the original message sender will be implemented // in the future @@ -1712,18 +1714,16 @@ SIPCall::setPeerUaVersion(std::string_view ua) } if (peerUserAgent_.empty()) { - JAMI_DBG("[call:%s] Set peer's User-Agent to [%.*s]", - getCallId().c_str(), - (int) ua.size(), - ua.data()); + JAMI_DEBUG("[call:{}] Set peer's User-Agent to [{}]", + getCallId(), + ua); } else if (not peerUserAgent_.empty()) { // Unlikely, but should be handled since we dont have control over the peer. // Even if it's unexpected, we still try to parse the UA version. - JAMI_WARN("[call:%s] Peer's User-Agent unexpectedly changed from [%s] to [%.*s]", - getCallId().c_str(), - peerUserAgent_.c_str(), - (int) ua.size(), - ua.data()); + JAMI_WARNING("[call:{}] Peer's User-Agent unexpectedly changed from [{}] to [{}]", + getCallId(), + peerUserAgent_, + ua); } peerUserAgent_ = ua; @@ -1755,13 +1755,13 @@ SIPCall::setPeerUaVersion(std::string_view ua) } if (version.empty()) { - JAMI_DBG("[call:%s] Could not parse peer's version", getCallId().c_str()); + JAMI_DEBUG("[call:{}] Could not parse peer's version", getCallId()); return; } auto peerVersion = split_string_to_unsigned(version, '.'); if (peerVersion.size() > 4u) { - JAMI_WARN("[call:%s] Could not parse peer's version", getCallId().c_str()); + JAMI_WARNING("[call:{}] Could not parse peer's version", getCallId()); return; } @@ -1769,35 +1769,40 @@ SIPCall::setPeerUaVersion(std::string_view ua) peerSupportMultiStream_ = Account::meetMinimumRequiredVersion(peerVersion, MULTISTREAM_REQUIRED_VERSION); if (not peerSupportMultiStream_) { - JAMI_DBG( - "Peer's version [%.*s] does not support multi-stream. Min required version: [%.*s]", - (int) version.size(), - version.data(), - (int) MULTISTREAM_REQUIRED_VERSION_STR.size(), - MULTISTREAM_REQUIRED_VERSION_STR.data()); + JAMI_DEBUG( + "Peer's version [{}] does not support multi-stream. Min required version: [{}]", + version, + MULTISTREAM_REQUIRED_VERSION_STR); } + + // Check if peer's version is at least 13.11.0 to enable multi-audio-stream. + peerSupportMultiAudioStream_ = Account::meetMinimumRequiredVersion(peerVersion, + MULTIAUDIO_REQUIRED_VERSION); + if (not peerSupportMultiAudioStream_) { + JAMI_DEBUG( + "Peer's version [{}] does not support multi-audio-stream. Min required version: [{}]", + version, + MULTIAUDIO_REQUIRED_VERSION_STR); + } + // Check if peer's version is at least 13.3.0 to enable multi-ICE. peerSupportMultiIce_ = Account::meetMinimumRequiredVersion(peerVersion, MULTIICE_REQUIRED_VERSION); if (not peerSupportMultiIce_) { - JAMI_DBG("Peer's version [%.*s] does not support more than 2 ICE medias. Min required " - "version: [%.*s]", - (int) version.size(), - version.data(), - (int) MULTIICE_REQUIRED_VERSION_STR.size(), - MULTIICE_REQUIRED_VERSION_STR.data()); + JAMI_DEBUG("Peer's version [{}] does not support more than 2 ICE medias. Min required " + "version: [{}]", + version, + MULTIICE_REQUIRED_VERSION_STR); } // Check if peer's version supports re-invite without ICE renegotiation. peerSupportReuseIceInReinv_ = Account::meetMinimumRequiredVersion(peerVersion, REUSE_ICE_IN_REINVITE_REQUIRED_VERSION); if (not peerSupportReuseIceInReinv_) { - JAMI_DBG("Peer's version [%.*s] does not support re-invite without ICE renegotiation. Min " - "required version: [%.*s]", - (int) version.size(), - version.data(), - (int) REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR.size(), - REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR.data()); + JAMI_DEBUG("Peer's version [%.*s] does not support re-invite without ICE renegotiation. Min " + "required version: [%.*s]", + version, + REUSE_ICE_IN_REINVITE_REQUIRED_VERSION_STR); } } @@ -2538,7 +2543,7 @@ SIPCall::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList) // Disable video if disabled in the account. auto account = getSIPAccount(); if (not account) { - JAMI_ERR("[call:%s] No account detected", getCallId().c_str()); + JAMI_ERROR("[call:{}] No account detected", getCallId()); return false; } if (not account->isVideoEnabled()) { @@ -2546,9 +2551,9 @@ SIPCall::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList) if (mediaAttr.type_ == MediaType::MEDIA_VIDEO) { // This an API misuse. The new medialist should not contain video // if it was disabled in the account settings. - JAMI_ERR("[call:%s] New media has video, but it's disabled in the account. " - "Ignoring the change request!", - getCallId().c_str()); + JAMI_ERROR("[call:{}] New media has video, but it's disabled in the account. " + "Ignoring the change request!", + getCallId()); return false; } } @@ -2558,18 +2563,32 @@ SIPCall::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList) // media list is different from the current media list, the media // change request will be ignored. if (not peerSupportMultiStream_ and rtpStreams_.size() != mediaAttrList.size()) { - JAMI_WARN("[call:%s] Peer does not support multi-stream. Media change request ignored", - getCallId().c_str()); + JAMI_WARNING("[call:{}] Peer does not support multi-stream. Media change request ignored", + getCallId()); return false; } + // If the peer does not support multi-audio-stream and the new + // media list has more than one audio. Ignore the one that comes from a file. + if (not peerSupportMultiAudioStream_ and rtpStreams_.size() != mediaAttrList.size() and hasFileSharing) { + JAMI_WARNING("[call:{}] Peer does not support multi-audio-stream. New Audio will be ignored", + getCallId()); + for (auto it = mediaAttrList.begin(); it != mediaAttrList.end();) { + if (it->type_ == MediaType::MEDIA_AUDIO and !it->sourceUri_.empty() and mediaPlayerId_ == it->sourceUri_) { + it = mediaAttrList.erase(it); + continue; + } + ++it; + } + } + // If peer doesn't support multiple ice, keep only the last audio/video // This keep the old behaviour (if sharing both camera + sharing a file, will keep the shared file) if (!peerSupportMultiIce_) { if (mediaList.size() > 2) - JAMI_WARN("[call:%s] Peer does not support more than 2 ICE medias. Media change " - "request modified", - getCallId().c_str()); + JAMI_WARNING("[call:{}] Peer does not support more than 2 ICE medias. Media change " + "request modified", + getCallId()); MediaAttribute audioAttr; MediaAttribute videoAttr; auto hasVideo = false, hasAudio = false; @@ -2592,14 +2611,14 @@ SIPCall::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList) if (hasVideo) mediaAttrList.emplace_back(videoAttr); } - JAMI_DBG("[call:%s] Requesting media change. List of new media:", getCallId().c_str()); + JAMI_DEBUG("[call:{}] Requesting media change. List of new media:", getCallId()); unsigned idx = 0; for (auto const& newMediaAttr : mediaAttrList) { - JAMI_DBG("[call:%s] Media @%u: %s", - getCallId().c_str(), - idx++, - newMediaAttr.toString(true).c_str()); + JAMI_DEBUG("[call:{}] Media @{:d}: {}", + getCallId(), + idx++, + newMediaAttr.toString(true)); } auto needReinvite = isReinviteRequired(mediaAttrList); @@ -2609,12 +2628,12 @@ SIPCall::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList) return false; if (needReinvite) { - JAMI_DBG("[call:%s] Media change requires a new negotiation (re-invite)", - getCallId().c_str()); + JAMI_DEBUG("[call:{}] Media change requires a new negotiation (re-invite)", + getCallId()); requestReinvite(mediaAttrList, needNewIce); } else { - JAMI_DBG("[call:%s] Media change DOES NOT require a new negotiation (re-invite)", - getCallId().c_str()); + JAMI_DEBUG("[call:{}] Media change DOES NOT require a new negotiation (re-invite)", + getCallId()); reportMediaNegotiationStatus(); } @@ -2638,6 +2657,20 @@ SIPCall::getMediaAttributeList() const return mediaList; } +std::map<std::string, bool> +SIPCall::getAudioStreams() const +{ + std::map<std::string, bool> audioMedias {}; + auto medias = getMediaAttributeList(); + for (const auto& media : medias) { + if (media.type_ == MEDIA_AUDIO) { + auto label = fmt::format("{}_{}", getCallId(), media.label_); + audioMedias.emplace(label, media.muted_); + } + } + return audioMedias; +} + void SIPCall::onMediaNegotiationComplete() { @@ -3125,6 +3158,7 @@ SIPCall::enterConference(std::shared_ptr<Conference> conference) for (const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO)) std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->enterConference(*conference); #endif + conference->bindParticipant(getCallId()); #ifdef ENABLE_PLUGIN clearCallAVStreams(); @@ -3138,9 +3172,14 @@ SIPCall::exitConference() JAMI_DBG("[call:%s] Leaving conference", getCallId().c_str()); auto const hasAudio = !getRtpSessionList(MediaType::MEDIA_AUDIO).empty(); - if (hasAudio && !isCaptureDeviceMuted(MediaType::MEDIA_AUDIO)) { + if (hasAudio) { auto& rbPool = Manager::instance().getRingBufferPool(); - rbPool.bindCallID(getCallId(), RingBufferPool::DEFAULT_ID); + auto medias = getAudioStreams(); + for (const auto& media : medias) { + if (!media.second) { + rbPool.bindRingbuffers(media.first, RingBufferPool::DEFAULT_ID); + } + } rbPool.flush(RingBufferPool::DEFAULT_ID); } #ifdef ENABLE_VIDEO @@ -3479,6 +3518,7 @@ SIPCall::merge(Call& call) localVideoPort_ = subcall.localVideoPort_; peerUserAgent_ = subcall.peerUserAgent_; peerSupportMultiStream_ = subcall.peerSupportMultiStream_; + peerSupportMultiAudioStream_ = subcall.peerSupportMultiAudioStream_; peerSupportMultiIce_ = subcall.peerSupportMultiIce_; peerAllowedMethods_ = subcall.peerAllowedMethods_; peerSupportReuseIceInReinv_ = subcall.peerSupportReuseIceInReinv_; diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h index 8dee71e1b64ac6b83118e9324bb0b8044194a00c..1dc3bb64b255fe0c00766385e4a10864563e36e0 100644 --- a/src/sip/sipcall.h +++ b/src/sip/sipcall.h @@ -142,6 +142,7 @@ public: void removeCall() override; void muteMedia(const std::string& mediaType, bool isMuted) override; std::vector<MediaAttribute> getMediaAttributeList() const override; + std::map<std::string, bool> getAudioStreams() const override; void restartMediaSender() override; std::shared_ptr<SystemCodecInfo> getAudioCodec() const override; std::shared_ptr<SystemCodecInfo> getVideoCodec() const override; @@ -457,6 +458,8 @@ private: std::string peerUserAgent_ {}; // Flag to indicate if the peer's Daemon version supports multi-stream. bool peerSupportMultiStream_ {false}; + // Flag to indicate if the peer's Daemon version supports multi-stream. + bool peerSupportMultiAudioStream_ {false}; // Flag to indicate if the peer's Daemon version can negotiate more than 2 ICE medias bool peerSupportMultiIce_ {false}; diff --git a/test/.gitignore b/test/.gitignore index fdeec9068bc9a8c6cc20248b9b1a028dfb13e40f..57b9705b07a70b7da593ebafa0c864cf3c4b30f8 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -9,4 +9,5 @@ ut_* .dirstamp jami-sample.yml +jami-sample.bak jami-sample.yml.bak diff --git a/test/unitTest/call/recorder.cpp b/test/unitTest/call/recorder.cpp index 7d914950436497883d7c85ad9598695a29e131db..b0f84af9338c08c0b8304d6cee9801f2e5ef9661 100644 --- a/test/unitTest/call/recorder.cpp +++ b/test/unitTest/call/recorder.cpp @@ -131,9 +131,10 @@ RecorderTest::setUp() void RecorderTest::tearDown() { + player.reset(); + jami::closeMediaPlayer(playerId); libjami::setIsAlwaysRecording(false); dhtnet::fileutils::removeAll(recordDir); - player.reset(); wait_for_removal_of({aliceId, bobId}); }