Skip to content
Snippets Groups Projects
Commit 2e3b0e00 authored by Guillaume Roguez's avatar Guillaume Roguez Committed by Anthony Léonard
Browse files

sipcall: use dual-buffered ice transport


This patch refactor the ICE transport system used for media streams
by using a dual-buffered like transport:
- one temporary used during the connection establishment
- one used when established and used by other components

When the established is used, the temporary is used to hide all the
connection stuff that can take time to succeed.

This has the (good) side-effect to resolve an issue on holding media
during a call (unhold doesn't work, media using an old transport).

Change-Id: I6b634443e06ece2dec2dd26a4b7ea429319d98d2
Reviewed-by: default avatarAnthony Léonard <anthony.leonard@savoirfairelinux.com>
parent c04cde48
No related branches found
No related tags found
No related merge requests found
...@@ -726,25 +726,28 @@ SIPCall::onPeerRinging() ...@@ -726,25 +726,28 @@ SIPCall::onPeerRinging()
void void
SIPCall::setupLocalSDPFromIce() SIPCall::setupLocalSDPFromIce()
{ {
if (not mediaTransport_) { auto media_tr = getIceMediaTransport();
RING_WARN("[call:%s] null icetransport, no attributes added to SDP", getCallId().c_str());
if (not media_tr) {
RING_WARN("[call:%s] no media ICE transport, SDP not changed", getCallId().c_str());
return; return;
} }
// we need an initialized ICE to progress further // we need an initialized ICE to progress further
if (mediaTransport_->waitForInitialization(DEFAULT_ICE_INIT_TIMEOUT) <= 0) { if (media_tr->waitForInitialization(DEFAULT_ICE_INIT_TIMEOUT) <= 0) {
RING_ERR("[call:%s] Medias' ICE init failed", getCallId().c_str()); RING_ERR("[call:%s] Medias' ICE init failed", getCallId().c_str());
return; return;
} }
sdp_->addIceAttributes(mediaTransport_->getLocalAttributes()); RING_WARN("[call:%s] fill SDP with ICE transport %p", getCallId().c_str(), media_tr);
sdp_->addIceAttributes(media_tr->getLocalAttributes());
// Add video and audio channels // Add video and audio channels
sdp_->addIceCandidates(SDP_AUDIO_MEDIA_ID, mediaTransport_->getLocalCandidates(ICE_AUDIO_RTP_COMPID)); sdp_->addIceCandidates(SDP_AUDIO_MEDIA_ID, media_tr->getLocalCandidates(ICE_AUDIO_RTP_COMPID));
sdp_->addIceCandidates(SDP_AUDIO_MEDIA_ID, mediaTransport_->getLocalCandidates(ICE_AUDIO_RTCP_COMPID)); sdp_->addIceCandidates(SDP_AUDIO_MEDIA_ID, media_tr->getLocalCandidates(ICE_AUDIO_RTCP_COMPID));
#ifdef RING_VIDEO #ifdef RING_VIDEO
sdp_->addIceCandidates(SDP_VIDEO_MEDIA_ID, mediaTransport_->getLocalCandidates(ICE_VIDEO_RTP_COMPID)); sdp_->addIceCandidates(SDP_VIDEO_MEDIA_ID, media_tr->getLocalCandidates(ICE_VIDEO_RTP_COMPID));
sdp_->addIceCandidates(SDP_VIDEO_MEDIA_ID, mediaTransport_->getLocalCandidates(ICE_VIDEO_RTCP_COMPID)); sdp_->addIceCandidates(SDP_VIDEO_MEDIA_ID, media_tr->getLocalCandidates(ICE_VIDEO_RTCP_COMPID));
#endif #endif
} }
...@@ -752,12 +755,13 @@ std::vector<IceCandidate> ...@@ -752,12 +755,13 @@ std::vector<IceCandidate>
SIPCall::getAllRemoteCandidates() SIPCall::getAllRemoteCandidates()
{ {
std::vector<IceCandidate> rem_candidates; std::vector<IceCandidate> rem_candidates;
auto media_tr = getIceMediaTransport();
auto addSDPCandidates = [this](unsigned sdpMediaId, auto addSDPCandidates = [&, this](unsigned sdpMediaId,
std::vector<IceCandidate>& out) { std::vector<IceCandidate>& out) {
IceCandidate cand; IceCandidate cand;
for (auto& line : sdp_->getIceCandidates(sdpMediaId)) { for (auto& line : sdp_->getIceCandidates(sdpMediaId)) {
if (mediaTransport_->getCandidateFromSDP(line, cand)) { if (media_tr->getCandidateFromSDP(line, cand)) {
RING_DBG("[call:%s] add remote ICE candidate: %s", getCallId().c_str(), line.c_str()); RING_DBG("[call:%s] add remote ICE candidate: %s", getCallId().c_str(), line.c_str());
out.emplace_back(cand); out.emplace_back(cand);
} }
...@@ -772,23 +776,6 @@ SIPCall::getAllRemoteCandidates() ...@@ -772,23 +776,6 @@ SIPCall::getAllRemoteCandidates()
return rem_candidates; return rem_candidates;
} }
bool
SIPCall::startIce()
{
if (not mediaTransport_ or mediaTransport_->isFailed() or not mediaTransport_->isInitialized())
return false;
if (mediaTransport_->isStarted()) {
RING_DBG("[call:%s] ICE already started", getCallId().c_str());
return true;
}
auto rem_ice_attrs = sdp_->getIceAttributes();
if (rem_ice_attrs.ufrag.empty() or rem_ice_attrs.pwd.empty()) {
RING_ERR("[call:%s] ICE empty attributes", getCallId().c_str());
return false;
}
return mediaTransport_->start(rem_ice_attrs, getAllRemoteCandidates());
}
bool bool
SIPCall::useVideoCodec(const AccountVideoCodecInfo* codec) const SIPCall::useVideoCodec(const AccountVideoCodecInfo* codec) const
{ {
...@@ -949,43 +936,88 @@ SIPCall::muteMedia(const std::string& mediaType, bool mute) ...@@ -949,43 +936,88 @@ SIPCall::muteMedia(const std::string& mediaType, bool mute)
} }
} }
/// \brief Prepare media transport and launch media stream based on negotiated SDP
///
/// This method has to be called by link (ie SipVoIpLink) when SDP is negotiated and
/// media streams structures are knows.
/// In case of ICE transport used, the medias streams are launched asynchonously when
/// the transport is negotiated.
void void
SIPCall::onMediaUpdate() SIPCall::onMediaUpdate()
{ {
RING_WARN("[call:%s] onMediaUpdate", getCallId().c_str()); RING_WARN("[call:%s] medias changed", getCallId().c_str());
// If ICE is not used, start medias now
auto rem_ice_attrs = sdp_->getIceAttributes();
if (rem_ice_attrs.ufrag.empty() or rem_ice_attrs.pwd.empty()) {
RING_WARN("[call:%s] no remote ICE for medias", getCallId().c_str());
stopAllMedia(); stopAllMedia();
openPortsUPnP();
if (startIce()) {
if (not parent_.load())
waitForIceAndStartMedia();
} else {
RING_WARN("[call:%s] ICE not used for media", getCallId().c_str());
startAllMedia(); startAllMedia();
return;
} }
// If we are a subcall let the parent manage it after merge operation
if (not parent_.load())
waitForIceAndStartMedia();
} }
void void
SIPCall::waitForIceAndStartMedia() SIPCall::waitForIceAndStartMedia()
{ {
auto ice = mediaTransport_; // Initialization waiting task
auto iceTimeout = std::chrono::steady_clock::now() + std::chrono::seconds(10); auto weak_call = std::weak_ptr<SIPCall>(std::static_pointer_cast<SIPCall>(shared_from_this()));
auto wthis = std::weak_ptr<SIPCall>(std::static_pointer_cast<SIPCall>(shared_from_this())); Manager::instance().addTask([weak_call] {
Manager::instance().addTask([wthis,ice,iceTimeout] { // TODO: polling algo, to it by event
if (auto sthis = wthis.lock()) { if (auto call = weak_call.lock()) {
auto& this_ = *sthis; auto ice = call->getIceMediaTransport();
if (ice != this_.mediaTransport_) {
RING_WARN("[call:%s] ICE transport replaced", this_.getCallId().c_str()); if (ice->isFailed()) {
RING_ERR("[call:%s] Media ICE init failed", call->getCallId().c_str());
call->onFailure(EIO);
return false; return false;
} }
/* First step: wait for an ICE transport for SIP channel */
if (this_.mediaTransport_->isFailed() or std::chrono::steady_clock::now() >= iceTimeout) { if (!ice->isInitialized())
RING_DBG("[call:%s] ICE init failed (or timeout)", this_.getCallId().c_str()); return true;
this_.onFailure(ETIMEDOUT);
// Start transport on SDP data and wait for negotation
auto rem_ice_attrs = call->sdp_->getIceAttributes();
if (rem_ice_attrs.ufrag.empty() or rem_ice_attrs.pwd.empty()) {
RING_ERR("[call:%s] Media ICE attributes empty", call->getCallId().c_str());
call->onFailure(EIO);
return false;
}
if (not ice->start(rem_ice_attrs, call->getAllRemoteCandidates())) {
RING_ERR("[call:%s] Media ICE start failed", call->getCallId().c_str());
call->onFailure(EIO);
return false;
}
// Negotiation waiting task
Manager::instance().addTask([weak_call] {
if (auto call = weak_call.lock()) {
auto ice = call->getIceMediaTransport();
if (ice->isFailed()) {
RING_ERR("[call:%s] Media ICE negotation failed", call->getCallId().c_str());
call->onFailure(EIO);
return false; return false;
} }
if (not this_.mediaTransport_->isRunning())
if (not ice->isRunning())
return true; return true;
this_.startAllMedia();
// Nego succeed: move to the new media transport
call->stopAllMedia();
if (call->tmpMediaTransport_)
call->mediaTransport_ = std::move(call->tmpMediaTransport_);
call->startAllMedia();
return false;
}
return false;
});
return false;
} }
return false; return false;
}); });
...@@ -1004,6 +1036,7 @@ SIPCall::onReceiveOffer(const pjmedia_sdp_session* offer) ...@@ -1004,6 +1036,7 @@ SIPCall::onReceiveOffer(const pjmedia_sdp_session* offer)
setRemoteSdp(offer); setRemoteSdp(offer);
sdp_->startNegotiation(); sdp_->startNegotiation();
pjsip_inv_set_sdp_answer(inv.get(), sdp_->getLocalSdpSession()); pjsip_inv_set_sdp_answer(inv.get(), sdp_->getLocalSdpSession());
openPortsUPnP();
} }
void void
...@@ -1104,17 +1137,20 @@ SIPCall::InvSessionDeleter::operator ()(pjsip_inv_session* inv) const noexcept ...@@ -1104,17 +1137,20 @@ SIPCall::InvSessionDeleter::operator ()(pjsip_inv_session* inv) const noexcept
bool bool
SIPCall::initIceMediaTransport(bool master, unsigned channel_num) SIPCall::initIceMediaTransport(bool master, unsigned channel_num)
{ {
RING_DBG("[call:%s] create media ICE transport", getCallId().c_str());
auto& iceTransportFactory = Manager::instance().getIceTransportFactory(); auto& iceTransportFactory = Manager::instance().getIceTransportFactory();
mediaTransport_ = iceTransportFactory.createTransport(getCallId().c_str(), tmpMediaTransport_ = iceTransportFactory.createTransport(getCallId().c_str(),
channel_num, master, channel_num, master,
getAccount().getIceOptions()); getAccount().getIceOptions());
if (mediaTransport_) { if (tmpMediaTransport_) {
// Add account's public IP as host candidate
if (const auto& publicIP = getSIPAccount().getPublishedIpAddress()) { if (const auto& publicIP = getSIPAccount().getPublishedIpAddress()) {
for (unsigned compId = 1; compId <= mediaTransport_->getComponentCount(); ++compId) for (unsigned compId = 1; compId <= tmpMediaTransport_->getComponentCount(); ++compId)
mediaTransport_->registerPublicIP(compId, publicIP); tmpMediaTransport_->registerPublicIP(compId, publicIP);
} }
} }
return static_cast<bool>(mediaTransport_); return static_cast<bool>(tmpMediaTransport_);
} }
void void
...@@ -1136,10 +1172,10 @@ SIPCall::merge(Call& call) ...@@ -1136,10 +1172,10 @@ SIPCall::merge(Call& call)
localAudioPort_ = subcall.localAudioPort_; localAudioPort_ = subcall.localAudioPort_;
localVideoPort_ = subcall.localVideoPort_; localVideoPort_ = subcall.localVideoPort_;
mediaTransport_ = std::move(subcall.mediaTransport_); mediaTransport_ = std::move(subcall.mediaTransport_);
tmpMediaTransport_ = std::move(subcall.tmpMediaTransport_);
Call::merge(subcall); Call::merge(subcall);
if (mediaTransport_->isStarted())
waitForIceAndStartMedia(); waitForIceAndStartMedia();
} }
...@@ -1149,15 +1185,24 @@ SIPCall::setRemoteSdp(const pjmedia_sdp_session* sdp) ...@@ -1149,15 +1185,24 @@ SIPCall::setRemoteSdp(const pjmedia_sdp_session* sdp)
if (!sdp) if (!sdp)
return; return;
auto ice_attrs = Sdp::getIceAttributes(sdp); // If ICE is not used, start medias now
if (not ice_attrs.ufrag.empty() and not ice_attrs.pwd.empty()) { auto rem_ice_attrs = sdp_->getIceAttributes();
if (not mediaTransport_) { if (rem_ice_attrs.ufrag.empty() or rem_ice_attrs.pwd.empty()) {
RING_DBG("Initializing ICE transport"); RING_WARN("[call:%s] no ICE data in remote SDP", getCallId().c_str());
initIceMediaTransport(false); return;
}
if (not initIceMediaTransport(false)) {
// Fatal condition
// TODO: what's SIP rfc says about that?
// (same question in waitForIceAndStartMedia)
onFailure(EIO);
return;
} }
// WARNING: This call blocks! (need ice init done)
setupLocalSDPFromIce(); setupLocalSDPFromIce();
} }
}
bool bool
SIPCall::isIceRunning() const SIPCall::isIceRunning() const
......
...@@ -201,8 +201,6 @@ public: // NOT SIP RELATED (good candidates to be moved elsewhere) ...@@ -201,8 +201,6 @@ public: // NOT SIP RELATED (good candidates to be moved elsewhere)
void generateMediaPorts(); void generateMediaPorts();
bool startIce();
void startAllMedia(); void startAllMedia();
void openPortsUPnP(); void openPortsUPnP();
...@@ -217,8 +215,8 @@ public: // NOT SIP RELATED (good candidates to be moved elsewhere) ...@@ -217,8 +215,8 @@ public: // NOT SIP RELATED (good candidates to be moved elsewhere)
std::unique_ptr<IceSocket> newIceSocket(unsigned compId); std::unique_ptr<IceSocket> newIceSocket(unsigned compId);
std::shared_ptr<IceTransport> getIceMediaTransport() const { IceTransport* getIceMediaTransport() const {
return mediaTransport_; return tmpMediaTransport_ ? tmpMediaTransport_.get() : mediaTransport_.get();
} }
private: private:
...@@ -281,7 +279,10 @@ private: ...@@ -281,7 +279,10 @@ private:
unsigned int localVideoPort_ {0}; unsigned int localVideoPort_ {0};
///< Transport used for media streams ///< Transport used for media streams
std::shared_ptr<IceTransport> mediaTransport_ {}; std::shared_ptr<IceTransport> mediaTransport_;
///< Temporary transport for media. Replace mediaTransport_ when connected with success
std::shared_ptr<IceTransport> tmpMediaTransport_;
}; };
// Helpers // Helpers
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment