Commit e557d7ef authored by Adrien Béraud's avatar Adrien Béraud Committed by Guillaume Roguez

video: renegotiate when switching input

Refs #6774

Change-Id: Ic0a0ad9276e1be39c25249aad66b37e74a7a74fe
parent 191ced1d
......@@ -247,6 +247,8 @@ class Call : public Recordable, public std::enable_shared_from_this<Call> {
*/
virtual void hangup(int reason) = 0;
virtual void switchInput(const std::string&) {};
/**
* Refuse incoming call
*/
......
......@@ -289,6 +289,12 @@ startTone(int32_t start, int32_t type)
ring::Manager::instance().stopTone();
}
bool
switchInput(const std::string& callID, const std::string& resource)
{
ring::Manager::instance().switchInput(callID, resource);
}
void
setSASVerified(const std::string& /*callID*/)
{
......
......@@ -118,10 +118,15 @@ stopCamera()
bool
switchInput(const std::string& resource)
{
if (auto input = videoManager.videoInput.lock())
return input->switchInput(resource);
RING_WARN("Video input not initialized");
if (auto call = ring::Manager::instance().getCurrentCall()) {
// TODO remove this part when clients are updated to use CallManager::switchInput
call->switchInput(resource);
return true;
} else {
if (auto input = videoManager.videoInput.lock())
return input->switchInput(resource).valid();
RING_WARN("Video input not initialized");
}
return false;
}
......
......@@ -197,6 +197,8 @@ std::string getCurrentAudioCodecName(const std::string& callID);
void playDTMF(const std::string& key);
void startTone(int32_t start, int32_t type);
bool switchInput(const std::string& callID, const std::string& resource);
/* Security related methods */
void setSASVerified(const std::string& callID);
void resetSASVerified(const std::string& callID);
......
......@@ -2097,6 +2097,17 @@ ManagerImpl::getCurrentAudioDevicesIndex()
return v;
}
void
ManagerImpl::switchInput(const std::string& call_id, const std::string& res)
{
auto call = getCallFromCallID(call_id);
if (!call) {
RING_ERR("Call %s is NULL", call_id.c_str());
return;
}
call->switchInput(res);
}
int
ManagerImpl::isRingtoneEnabled(const std::string& id)
{
......
......@@ -555,6 +555,8 @@ class ManagerImpl {
bool isAGCEnabled() const;
void setAGCState(bool enabled);
void switchInput(const std::string& callid, const std::string& res);
/**
* Ringtone option.
* If ringtone is enabled, ringtone on incoming call use custom choice. If not, only standart tone.
......
......@@ -150,8 +150,10 @@ bool VideoInput::captureFrame()
void
VideoInput::createDecoder()
{
if (decOpts_.input.empty())
if (decOpts_.input.empty()) {
foundDecOpts_.set_value(decOpts_);
return;
}
decoder_ = new MediaDecoder();
......@@ -164,6 +166,8 @@ VideoInput::createDecoder()
RING_ERR("Could not open input \"%s\"", decOpts_.input.c_str());
delete decoder_;
decoder_ = nullptr;
//foundDecOpts_.set_exception(std::runtime_error("Could not open input"));
foundDecOpts_.set_value(decOpts_);
return;
}
......@@ -172,8 +176,13 @@ VideoInput::createDecoder()
RING_ERR("decoder IO startup failed");
delete decoder_;
decoder_ = nullptr;
//foundDecOpts_.set_exception(std::runtime_error("Could not read data"));
foundDecOpts_.set_value(decOpts_);
return;
}
decOpts_.width = decoder_->getWidth();
decOpts_.height = decoder_->getHeight();
foundDecOpts_.set_value(decOpts_);
}
void
......@@ -195,6 +204,12 @@ VideoInput::initCamera(const std::string& device)
return true;
}
static constexpr unsigned
round2pow(unsigned i, unsigned n)
{
return (i >> n) << n;
}
bool
VideoInput::initX11(std::string display)
{
......@@ -207,7 +222,11 @@ VideoInput::initX11(std::string display)
if (space != std::string::npos) {
std::istringstream iss(display.substr(space + 1));
char sep;
iss >> decOpts_.width >> sep >> decOpts_.height;
unsigned w, h;
iss >> w >> sep >> h;
// round to 8 pixel block
decOpts_.width = round2pow(w, 3);
decOpts_.height = round2pow(h, 3);
decOpts_.input = display.erase(space);
} else {
decOpts_.input = display;
......@@ -250,23 +269,26 @@ VideoInput::initFile(std::string path)
return true;
}
bool
std::future<DeviceParams>
VideoInput::switchInput(const std::string& resource)
{
RING_DBG("MRL: '%s'", resource.c_str());
if (switchPending_) {
RING_ERR("Video switch already requested");
return false;
return {};
}
std::promise<DeviceParams> p;
foundDecOpts_.swap(p);
// Switch off video input?
if (resource.empty()) {
clearOptions();
switchPending_ = true;
if (!loop_.isRunning())
loop_.start();
return true;
return foundDecOpts_.get_future();
}
// Supported MRL schemes
......@@ -274,11 +296,11 @@ VideoInput::switchInput(const std::string& resource)
const auto pos = resource.find(sep);
if (pos == std::string::npos)
return false;
return {};
const auto prefix = resource.substr(0, pos);
if ((pos + sep.size()) >= resource.size())
return false;
return {};
const auto suffix = resource.substr(pos + sep.size());
......@@ -304,7 +326,7 @@ VideoInput::switchInput(const std::string& resource)
else
RING_ERR("Failed to init input for MRL '%s'\n", resource.c_str());
return valid;
return valid ? foundDecOpts_.get_future() : std::future<DeviceParams> {};
}
int VideoInput::getWidth() const
......@@ -316,4 +338,7 @@ int VideoInput::getHeight() const
int VideoInput::getPixelFormat() const
{ return decoder_->getPixelFormat(); }
DeviceParams VideoInput::getParams() const
{ return decOpts_; }
}} // namespace ring::video
......@@ -41,6 +41,7 @@
#include <map>
#include <atomic>
#include <future>
#include <string>
namespace ring {
......@@ -59,8 +60,9 @@ public:
int getWidth() const;
int getHeight() const;
int getPixelFormat() const;
DeviceParams getParams() const;
bool switchInput(const std::string& resource);
std::future<DeviceParams> switchInput(const std::string& resource);
private:
NON_COPYABLE(VideoInput);
......@@ -72,6 +74,7 @@ private:
std::atomic<bool> switchPending_ = {false};
DeviceParams decOpts_;
std::promise<DeviceParams> foundDecOpts_;
bool emulateRate_ = false;
ThreadLoop loop_;
......
......@@ -68,6 +68,20 @@ void VideoRtpSession::startSender()
RING_WARN("Restarting video sender");
}
std::future<DeviceParams> newParams;
if (not conference_) {
videoLocal_ = getVideoCamera();
if (auto input = videoManager.videoInput.lock()) {
newParams = input->switchInput(input_);
if (newParams.valid())
localVideoParams_ = newParams.get();
else
RING_WARN("No valid new video parameters.");
} else {
RING_WARN("Can't lock video input");
}
}
try {
sender_.reset(
new VideoSender(getRemoteRtpUri(), localVideoParams_, local_, *socketPair_)
......@@ -123,9 +137,8 @@ void VideoRtpSession::start()
if (conference_)
setupConferenceVideoPipeline(conference_);
else if (sender_) {
videoLocal_ = getVideoCamera();
if (videoLocal_ and videoLocal_->attach(sender_.get()))
DRing::switchToCamera();
if (videoLocal_)
videoLocal_->attach(sender_.get());
} else {
videoLocal_.reset();
}
......@@ -150,9 +163,8 @@ void VideoRtpSession::start(std::unique_ptr<IceSocket> rtp_sock,
if (conference_)
setupConferenceVideoPipeline(conference_);
else if (sender_) {
videoLocal_ = getVideoCamera();
if (videoLocal_ and videoLocal_->attach(sender_.get()))
DRing::switchToCamera();
if (videoLocal_)
videoLocal_->attach(sender_.get());
} else {
videoLocal_.reset();
}
......
......@@ -66,12 +66,16 @@ public:
void unbindMixer();
void enterConference(Conference* conference);
void exitConference();
void switchInput(const std::string& input) {
input_ = input;
}
private:
void setupConferenceVideoPipeline(Conference *conference);
void startSender();
void startReceiver();
std::string input_;
DeviceParams localVideoParams_;
std::unique_ptr<VideoSender> sender_;
......
This diff is collapsed.
......@@ -128,7 +128,8 @@ class Sdp {
*/
bool createOffer(const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedAudioCodecs,
const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedVideoCodecs,
sip_utils::KeyExchangeProtocol);
sip_utils::KeyExchangeProtocol,
bool holding=false);
/*
* On receiving an invite outside a dialog, build the local offer and create the
......@@ -139,7 +140,8 @@ class Sdp {
void receiveOffer(const pjmedia_sdp_session* remote,
const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedAudioCodecs,
const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedVideoCodecs,
sip_utils::KeyExchangeProtocol);
sip_utils::KeyExchangeProtocol,
bool holding=false);
/**
* Start the sdp negotiation.
......@@ -204,11 +206,6 @@ class Sdp {
return localAudioControlPort_;
}
void addAttributeToLocalAudioMedia(const char *attr);
void removeAttributeFromLocalAudioMedia(const char *attr);
void addAttributeToLocalVideoMedia(const char *attr);
void removeAttributeFromLocalVideoMedia(const char *attr);
std::vector<MediaDescription>
getMediaSlots(const pjmedia_sdp_session* session, bool remote) const;
......@@ -231,12 +228,15 @@ class Sdp {
void addIceAttributes(const IceTransport::Attribute&& ice_attrs);
IceTransport::Attribute getIceAttributes() const;
static IceTransport::Attribute getIceAttributes(const pjmedia_sdp_session* session);
void addIceCandidates(unsigned media_index,
const std::vector<std::string>& cands);
std::vector<std::string> getIceCandidates(unsigned media_index) const;
void clearIce();
private:
friend class test::SDPTest;
......@@ -251,58 +251,53 @@ class Sdp {
*/
static std::string getFilteredSdp(const pjmedia_sdp_session* session, unsigned media_keep, unsigned pt_keep);
static void clearIce(pjmedia_sdp_session* session);
/**
* The pool to allocate memory
*/
std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> memPool_;
/** negotiator */
pjmedia_sdp_neg *negotiator_;
pjmedia_sdp_neg *negotiator_ {nullptr};
/**
* Local SDP
*/
pjmedia_sdp_session *localSession_;
pjmedia_sdp_session *localSession_ {nullptr};
/**
* Remote SDP
*/
pjmedia_sdp_session *remoteSession_;
pjmedia_sdp_session *remoteSession_ {nullptr};
/**
* The negotiated SDP remote session
* Explanation: each endpoint's offer is negotiated, and a new sdp offer results from this
* negotiation, with the compatible media from each part
*/
const pjmedia_sdp_session *activeLocalSession_;
const pjmedia_sdp_session *activeLocalSession_ {nullptr};
/**
* The negotiated SDP remote session
* Explanation: each endpoint's offer is negotiated, and a new sdp offer results from this
* negotiation, with the compatible media from each part
*/
const pjmedia_sdp_session *activeRemoteSession_;
const pjmedia_sdp_session *activeRemoteSession_ {nullptr};
/**
* Codec Map used for offer
*/
std::vector<std::shared_ptr<AccountCodecInfo>> audio_codec_list_;
std::vector<std::shared_ptr<AccountCodecInfo>> video_codec_list_;
/**
* The codecs that will be used by the session (after the SDP negotiation)
*/
std::vector<std::shared_ptr<AccountAudioCodecInfo>> sessionAudioMediaLocal_;
std::vector<std::shared_ptr<AccountAudioCodecInfo>> sessionAudioMediaRemote_;
std::vector<std::string> sessionVideoMedia_;
std::vector<std::shared_ptr<AccountCodecInfo>> audio_codec_list_;
std::vector<std::shared_ptr<AccountCodecInfo>> video_codec_list_;
std::string publishedIpAddr_;
pj_uint16_t publishedIpAddrType_;
int localAudioDataPort_;
int localAudioControlPort_;
int localVideoDataPort_;
int localVideoControlPort_;
int localAudioDataPort_ {0};
int localAudioControlPort_ {0};
int localVideoDataPort_ {0};
int localVideoControlPort_ {0};
SdesNegotiator sdesNego_;
std::string zrtpHelloHash_;
......@@ -313,7 +308,7 @@ class Sdp {
* Build the sdp media section
* Add rtpmap field if necessary
*/
pjmedia_sdp_media *setMediaDescriptorLines(bool audio, sip_utils::KeyExchangeProtocol);
pjmedia_sdp_media *setMediaDescriptorLines(bool audio, bool holding, sip_utils::KeyExchangeProtocol);
pjmedia_sdp_attr *generateSdesAttribute();
void setTelephoneEventRtpmap(pjmedia_sdp_media *med);
......@@ -330,7 +325,9 @@ class Sdp {
*/
int createLocalSession(const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedAudioCodecs,
const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedVideoCodecs,
sip_utils::KeyExchangeProtocol);
sip_utils::KeyExchangeProtocol,
bool holding);
/*
* Adds a sdes attribute to the given media section.
*
......
......@@ -149,29 +149,39 @@ SIPCall::getSIPAccount() const
void
SIPCall::setCallMediaLocal(const pj_sockaddr& localIP)
{
setLocalIp(localIP);
if (getLocalAudioPort() == 0
#ifdef RING_VIDEO
|| getLocalVideoPort() == 0
#endif
)
generateMediaPorts();
}
void
SIPCall::generateMediaPorts()
{
auto& account = getSIPAccount();
// Reference: http://www.cs.columbia.edu/~hgs/rtp/faq.html#ports
// We only want to set ports to new values if they haven't been set
if (getLocalAudioPort() == 0) {
const unsigned callLocalAudioPort = account.generateAudioPort();
setLocalAudioPort(callLocalAudioPort);
sdp_->setLocalPublishedAudioPort(callLocalAudioPort);
}
setLocalIp(localIP);
const unsigned callLocalAudioPort = account.generateAudioPort();
if (getLocalAudioPort() != 0)
account.releasePort(getLocalAudioPort());
setLocalAudioPort(callLocalAudioPort);
sdp_->setLocalPublishedAudioPort(callLocalAudioPort);
#ifdef RING_VIDEO
if (getLocalVideoPort() == 0) {
// https://projects.savoirfairelinux.com/issues/17498
const unsigned int callLocalVideoPort = account.generateVideoPort();
// this should already be guaranteed by SIPAccount
assert(getLocalAudioPort() != callLocalVideoPort);
setLocalVideoPort(callLocalVideoPort);
sdp_->setLocalPublishedVideoPort(callLocalVideoPort);
}
// https://projects.savoirfairelinux.com/issues/17498
const unsigned int callLocalVideoPort = account.generateVideoPort();
if (getLocalVideoPort() != 0)
account.releasePort(getLocalVideoPort());
// this should already be guaranteed by SIPAccount
assert(getLocalAudioPort() != callLocalVideoPort);
setLocalVideoPort(callLocalVideoPort);
sdp_->setLocalPublishedVideoPort(callLocalVideoPort);
#endif
}
......@@ -188,7 +198,19 @@ void SIPCall::setContactHeader(pj_str_t *contact)
int
SIPCall::SIPSessionReinvite()
{
// Generate new ports to receive the new media stream
// LibAV doesn't discriminate SSRCs and will be confused about Seq changes on a given port
generateMediaPorts();
sdp_->clearIce();
auto& acc = getSIPAccount();
sdp_->createOffer(acc.getActiveAccountCodecInfoList(MEDIA_AUDIO),
acc.getActiveAccountCodecInfoList(acc.isVideoEnabled() ? MEDIA_VIDEO : MEDIA_NONE),
acc.getSrtpKeyExchange(),
getState() == Call::HOLD);
initIceTransport(true);
setupLocalSDPFromIce();
pjmedia_sdp_session *local_sdp = sdp_->getLocalSdpSession();
pjsip_tx_data *tdata;
if (local_sdp and inv and inv->pool_prov
......@@ -541,18 +563,10 @@ SIPCall::onhold()
videortp_.stop();
#endif
sdp_->removeAttributeFromLocalAudioMedia("sendrecv");
sdp_->removeAttributeFromLocalAudioMedia("sendonly");
sdp_->addAttributeToLocalAudioMedia("sendonly");
#ifdef RING_VIDEO
sdp_->removeAttributeFromLocalVideoMedia("sendrecv");
sdp_->removeAttributeFromLocalVideoMedia("inactive");
sdp_->addAttributeToLocalVideoMedia("inactive");
#endif
if (SIPSessionReinvite() != PJ_SUCCESS)
RING_WARN("Reinvite failed");
if (getConnectionState() == Call::CONNECTED) {
if (SIPSessionReinvite() != PJ_SUCCESS)
RING_WARN("Reinvite failed");
}
}
void
......@@ -580,23 +594,22 @@ SIPCall::internalOffHold(const std::function<void()>& sdp_cb)
sdp_cb();
sdp_->removeAttributeFromLocalAudioMedia("sendrecv");
sdp_->removeAttributeFromLocalAudioMedia("sendonly");
sdp_->addAttributeToLocalAudioMedia("sendrecv");
#ifdef RING_VIDEO
sdp_->removeAttributeFromLocalVideoMedia("sendrecv");
sdp_->removeAttributeFromLocalVideoMedia("sendonly");
sdp_->removeAttributeFromLocalVideoMedia("inactive");
sdp_->addAttributeToLocalVideoMedia("sendrecv");
#endif
if (SIPSessionReinvite() != PJ_SUCCESS) {
RING_WARN("Reinvite failed, resuming hold");
onhold();
if (getConnectionState() == Call::CONNECTED) {
if (SIPSessionReinvite() != PJ_SUCCESS) {
RING_WARN("Reinvite failed, resuming hold");
onhold();
}
}
}
void
SIPCall::switchInput(const std::string& resource)
{
videoInput_ = resource;
if (SIPSessionReinvite() != PJ_SUCCESS)
RING_WARN("Reinvite failed");
}
void
SIPCall::peerHungup()
{
......@@ -697,8 +710,10 @@ SIPCall::getAllRemoteCandidates()
std::vector<IceCandidate>& out) {
IceCandidate cand;
for (auto& line : sdp_->getIceCandidates(sdpMediaId)) {
if (iceTransport_->getCandidateFromSDP(line, cand))
if (iceTransport_->getCandidateFromSDP(line, cand)) {
RING_ERR("Remote candidate: %s", line.c_str());
out.emplace_back(cand);
}
}
};
......@@ -796,7 +811,13 @@ SIPCall::startAllMedia()
);
RING_DBG("[REMOTE] SDP: \n %s", remote.receiving_sdp.c_str());
RING_DBG("####################################");
#ifdef RING_VIDEO
if (local.type == MEDIA_VIDEO) {
if (videoInput_.empty())
videoInput_ = "v4l2://" + videoManager.videoDeviceMonitor.getDefaultDevice();
videortp_.switchInput(videoInput_);
}
#endif
rtp->updateMedia(local, remote);
if (isIceRunning()) {
rtp->start(newIceSocket(ice_comp_id + 0),
......@@ -820,13 +841,19 @@ SIPCall::stopAllMedia()
void
SIPCall::onMediaUpdate()
{
RING_WARN("SIPCall::onMediaUpdate");
stopAllMedia();
openPortsUPnP();
if (startIce()) {
auto this_ = std::static_pointer_cast<SIPCall>(shared_from_this());
auto iceTimeout = std::chrono::steady_clock::now() + std::chrono::seconds {10};
auto ice = iceTransport_;
auto iceTimeout = std::chrono::steady_clock::now() + std::chrono::seconds(10);
Manager::instance().addTask([=] {
if (ice != this_->iceTransport_) {
RING_ERR("ICE transport replaced");
return false;
}
/* First step: wait for an ICE transport for SIP channel */
if (this_->iceTransport_->isFailed() or std::chrono::steady_clock::now() >= iceTimeout) {
RING_DBG("ice init failed (or timeout)");
......@@ -846,6 +873,26 @@ SIPCall::onMediaUpdate()
}
}
void
SIPCall::onReceiveOffer(const pjmedia_sdp_session* offer)
{
sdp_->clearIce();
auto& acc = getSIPAccount();
sdp_->receiveOffer(offer,
acc.getActiveAccountCodecInfoList(MEDIA_AUDIO),
acc.getActiveAccountCodecInfoList(acc.isVideoEnabled() ? MEDIA_VIDEO : MEDIA_NONE),
acc.getSrtpKeyExchange(),
getState() == Call::HOLD
);
auto ice_attrs = Sdp::getIceAttributes(offer);
if (not ice_attrs.ufrag.empty() and not ice_attrs.pwd.empty()) {
initIceTransport(false);
setupLocalSDPFromIce();
}
sdp_->startNegotiation();
pjsip_inv_set_sdp_answer(inv.get(), sdp_->getLocalSdpSession());
}
void
SIPCall::openPortsUPnP()
{
......
......@@ -53,6 +53,7 @@
struct pjsip_evsub;
struct pjsip_inv_session;
struct pjmedia_sdp_session;
namespace ring {
......@@ -126,6 +127,8 @@ class SIPCall : public Call
void setCallMediaLocal(const pj_sockaddr& localIP);
void generateMediaPorts();
void setContactHeader(pj_str_t *contact);
void setTransport(const std::shared_ptr<SipTransport>& t) {
......@@ -152,6 +155,8 @@ class SIPCall : public Call
void offhold();
void switchInput(const std::string& resource);
void peerHungup();
void carryingDTMFdigits(char code);
......@@ -191,6 +196,8 @@ class SIPCall : public Call
void onMediaUpdate();
void onReceiveOffer(const pjmedia_sdp_session *offer);
void openPortsUPnP();
virtual std::map<std::string, std::string> getDetails() const;
......@@ -218,6 +225,8 @@ class SIPCall : public Call
* Video Rtp Session factory
*/
video::VideoRtpSession videortp_;
std::string videoInput_;
#endif
bool srtpEnabled_ {false};
......
......@@ -874,18 +874,7 @@ sdp_request_offer_cb(pjsip_inv_session *inv, const pjmedia_sdp_session *offer)
if (!call_ptr)
return;
auto call = std::static_pointer_cast<SIPCall>(call_ptr->shared_from_this());
const auto& account = call->getSIPAccount();
auto& localSDP = call->getSDP();
localSDP.receiveOffer(offer,
account.getActiveAccountCodecInfoList(MEDIA_AUDIO),
account.getActiveAccountCodecInfoList(MEDIA_VIDEO),
account.getSrtpKeyExchange()
);
localSDP.startNegotiation();
pjsip_inv_set_sdp_answer(inv, localSDP.getLocalSdpSession());
call->onReceiveOffer(offer);
}
static void
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment