filesharings: integrate audio stream

GitLab: #485
Change-Id: I0ae7c23da2a1f2384699639cc0de58f8f05b33ec
This commit is contained in:
Aline Gondim Santos
2023-09-27 18:22:17 -03:00
committed by Sébastien Blin
parent 809600018b
commit a5a46c0385
26 changed files with 521 additions and 415 deletions

View File

@ -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)

View File

@ -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

View File

@ -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'

View File

@ -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;

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -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))

View File

@ -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_ {};

View File

@ -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&

View File

@ -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

View File

@ -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_;

View File

@ -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);

View File

@ -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_;

View File

@ -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);

View File

@ -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;

View File

@ -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.

View File

@ -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

View File

@ -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_ {};

View File

@ -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

View File

@ -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();
}

View File

@ -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_;

View File

@ -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_;

View File

@ -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};

1
test/.gitignore vendored
View File

@ -9,4 +9,5 @@ ut_*
.dirstamp
jami-sample.yml
jami-sample.bak
jami-sample.yml.bak

View File

@ -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});
}