mirror of
https://git.jami.net/savoirfairelinux/jami-daemon.git
synced 2025-08-12 22:09:25 +08:00
filesharings: integrate audio stream
GitLab: #485 Change-Id: I0ae7c23da2a1f2384699639cc0de58f8f05b33ec
This commit is contained in:

committed by
Sébastien Blin

parent
809600018b
commit
a5a46c0385
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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))
|
||||
|
@ -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_ {};
|
||||
|
@ -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&
|
||||
|
@ -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
|
||||
|
@ -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_;
|
||||
|
@ -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);
|
||||
|
@ -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_;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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_ {};
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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_;
|
||||
|
@ -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_;
|
||||
|
@ -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
1
test/.gitignore
vendored
@ -9,4 +9,5 @@ ut_*
|
||||
|
||||
.dirstamp
|
||||
jami-sample.yml
|
||||
jami-sample.bak
|
||||
jami-sample.yml.bak
|
||||
|
@ -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});
|
||||
}
|
||||
|
Reference in New Issue
Block a user