diff --git a/src/call.h b/src/call.h index 605aba22ba6de74129f1c5c9447d369964621cb9..c74ac8ceb9190e807071492a9c9cf000c315be2c 100644 --- a/src/call.h +++ b/src/call.h @@ -299,7 +299,7 @@ public: */ virtual bool offhold(OnReadyCb&& cb) = 0; - virtual void sendKeyframe() = 0; + virtual void sendKeyframe(int streamIdx = -1) = 0; /** * Check wether ICE is enabled for media diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp index fc037ae4ba6379301ba138e85e593336b8dae9cf..0654edd3e8fc87a0171e6d1f4168e80032867c3d 100644 --- a/src/sip/sipcall.cpp +++ b/src/sip/sipcall.cpp @@ -250,17 +250,18 @@ SIPCall::configureRtpSession(const std::shared_ptr<RtpSession>& rtpSession, #ifdef ENABLE_VIDEO if (localMedia.type == MediaType::MEDIA_VIDEO) { auto videoRtp = std::dynamic_pointer_cast<video::VideoRtpSession>(rtpSession); - assert(videoRtp); - videoRtp->setRequestKeyFrameCallback([w = weak()] { - runOnMainThread([w] { + assert(videoRtp && mediaAttr); + auto streamIdx = findRtpStreamIndex(mediaAttr->label_); + videoRtp->setRequestKeyFrameCallback([w = weak(), streamIdx] { + runOnMainThread([w = std::move(w), streamIdx] { if (auto thisPtr = w.lock()) - thisPtr->requestKeyframe(); + thisPtr->requestKeyframe(streamIdx); }); }); - videoRtp->setChangeOrientationCallback([w = weak()](int angle) { - runOnMainThread([w, angle] { + videoRtp->setChangeOrientationCallback([w = weak(), streamIdx](int angle) { + runOnMainThread([w, angle, streamIdx] { if (auto thisPtr = w.lock()) - thisPtr->setVideoOrientation(angle); + thisPtr->setVideoOrientation(streamIdx, angle); }); }); } @@ -602,17 +603,21 @@ SIPCall::updateRecState(bool state) } void -SIPCall::requestKeyframe() +SIPCall::requestKeyframe(int streamIdx) { auto now = clock::now(); if ((now - lastKeyFrameReq_) < MS_BETWEEN_2_KEYFRAME_REQUEST and lastKeyFrameReq_ != time_point::min()) return; - constexpr auto BODY = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" - "<media_control><vc_primitive><to_encoder>" - "<picture_fast_update/>" - "</to_encoder></vc_primitive></media_control>"sv; + std::string streamIdPart; + if (streamIdx != -1) + streamIdPart = fmt::format("<stream_id>{}</stream_id>", streamIdx); + std::string BODY = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + "<media_control><vc_primitive> " + + streamIdPart + "<to_encoder>" + + "<picture_fast_update/>" + "</to_encoder></vc_primitive></media_control>"; JAMI_DBG("Sending video keyframe request via SIP INFO"); try { sendSIPInfo(BODY, "media_control+xml"); @@ -1441,16 +1446,18 @@ SIPCall::carryingDTMFdigits(char code) } void -SIPCall::setVideoOrientation(int rotation) +SIPCall::setVideoOrientation(int streamIdx, int rotation) { + std::string streamIdPart; + if (streamIdx != -1) + streamIdPart = fmt::format("<stream_id>{}</stream_id>", streamIdx); std::string sip_body = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" "<media_control><vc_primitive><to_encoder>" "<device_orientation=" - + std::to_string(-rotation) - + "/>" - "</to_encoder></vc_primitive></media_control>"; + + std::to_string(-rotation) + "/>" + "</to_encoder>" + streamIdPart + + "</vc_primitive></media_control>"; - JAMI_DBG("Sending device orientation via SIP INFO %d", rotation); + JAMI_DBG("Sending device orientation via SIP INFO %d for stream %u", rotation, streamIdx); sendSIPInfo(sip_body, "media_control+xml"); } @@ -1586,14 +1593,23 @@ SIPCall::onAnswered() } void -SIPCall::sendKeyframe() +SIPCall::sendKeyframe(int streamIdx) { #ifdef ENABLE_VIDEO - dht::ThreadPool::computation().run([w = weak()] { + dht::ThreadPool::computation().run([w = weak(), streamIdx] { if (auto sthis = w.lock()) { JAMI_DBG("handling picture fast update request"); - for (const auto& videoRtp : sthis->getRtpSessionList(MediaType::MEDIA_VIDEO)) - std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->forceKeyFrame(); + if (streamIdx == -1) { + for (const auto& videoRtp : sthis->getRtpSessionList(MediaType::MEDIA_VIDEO)) + std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->forceKeyFrame(); + } else if (streamIdx > -1 && streamIdx < sthis->rtpStreams_.size()) { + // Apply request for wanted stream + auto& stream = sthis->rtpStreams_[streamIdx]; + if (stream.rtpSession_ + && stream.rtpSession_->getMediaType() == MediaType::MEDIA_VIDEO) + std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_) + ->forceKeyFrame(); + } } }); #endif @@ -2196,9 +2212,8 @@ SIPCall::updateRemoteMedia() remoteMedia->toString().c_str()); rtpStream.rtpSession_->setMuted(remoteMedia->muted_, RtpSession::Direction::RECV); // Request a key-frame if we are un-muting the video - if (not remoteMedia->muted_) { - requestKeyframe(); - } + if (not remoteMedia->muted_) + requestKeyframe(findRtpStreamIndex(remoteMedia->label_)); } } } @@ -2993,12 +3008,19 @@ SIPCall::exitConference() #ifdef ENABLE_VIDEO void -SIPCall::setRotation(int rotation) +SIPCall::setRotation(int streamIdx, int rotation) { rotation_ = rotation; - // For now, only apply rotation on all videos - for (auto const& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO)) - std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->setRotation(rotation); + if (streamIdx == -1) { + for (const auto& videoRtp : getRtpSessionList(MediaType::MEDIA_VIDEO)) + std::static_pointer_cast<video::VideoRtpSession>(videoRtp)->setRotation(rotation); + } else if (streamIdx > -1 && streamIdx < rtpStreams_.size()) { + // Apply request for wanted stream + auto& stream = rtpStreams_[streamIdx]; + if (stream.rtpSession_ && stream.rtpSession_->getMediaType() == MediaType::MEDIA_VIDEO) + std::static_pointer_cast<video::VideoRtpSession>(stream.rtpSession_) + ->setRotation(rotation); + } } void diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h index 5568a1726d2989b13be53eb0647cd384a94a89cd..5a642fdfeb6e9f94e16453946b03ade7d2d134b2 100644 --- a/src/sip/sipcall.h +++ b/src/sip/sipcall.h @@ -142,7 +142,7 @@ public: void restartMediaSender() override; std::shared_ptr<AccountCodecInfo> getAudioCodec() const override; std::shared_ptr<AccountCodecInfo> getVideoCodec() const override; - void sendKeyframe() override; + void sendKeyframe(int streamIdx = -1) override; bool isIceEnabled() const override; std::map<std::string, std::string> getDetails() const override; void enterConference(std::shared_ptr<Conference> conference) override; @@ -236,7 +236,7 @@ public: void sendSIPInfo(std::string_view body, std::string_view subtype); - void requestKeyframe(); + void requestKeyframe(int streamIdx = -1); void updateRecState(bool state) override; @@ -275,7 +275,7 @@ public: std::shared_ptr<AudioRtpSession> getAudioRtp() const; #ifdef ENABLE_VIDEO - void setRotation(int rotation); + void setRotation(int streamIdx, int rotation); #endif // Get the list of current RTP sessions std::vector<std::shared_ptr<RtpSession>> getRtpSessionList( @@ -327,9 +327,10 @@ private: /** * Send device orientation through SIP INFO - * @param rotation Device orientation (0/90/180/270) (counterclockwise) + * @param streamIdx The stream to rotate + * @param rotation Device orientation (0/90/180/270) (counterclockwise) */ - void setVideoOrientation(int rotation); + void setVideoOrientation(int streamIdx, int rotation); mutable std::mutex transportMtx_ {}; diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp index 43534721b21c3d97728677ee61f9e113d21a0115..95cf5ccb3455bc291b77682a0d0e49e489824761 100644 --- a/src/sip/sipvoiplink.cpp +++ b/src/sip/sipvoiplink.cpp @@ -1169,12 +1169,29 @@ handleMediaControl(SIPCall& call, pjsip_msg_body* body) /* Apply and answer the INFO request */ static constexpr auto PICT_FAST_UPDATE = "picture_fast_update"sv; + static constexpr auto STREAM_ID = "stream_id"sv; static constexpr auto DEVICE_ORIENTATION = "device_orientation"sv; static constexpr auto RECORDING_STATE = "recording_state"sv; static constexpr auto MUTE_STATE = "mute_state"sv; + int streamIdx = -1; + if (body_msg.find(STREAM_ID) != std::string_view::npos) { + // Note: here we use the index of the RTP stream, not it's label! + // Indeed, both side will have different labels as they have different call ids + static const std::regex STREAMID_REGEX("<stream_id>([0-9]+)</stream_id>"); + std::svmatch matched_pattern; + std::regex_search(body_msg, matched_pattern, STREAMID_REGEX); + if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) { + try { + streamIdx = std::stoi(matched_pattern[1]); + } catch (const std::exception& e) { + JAMI_WARN("Error parsing stream index: %s", e.what()); + } + } + } + if (body_msg.find(PICT_FAST_UPDATE) != std::string_view::npos) { - call.sendKeyframe(); + call.sendKeyframe(streamIdx); return true; } else if (body_msg.find(DEVICE_ORIENTATION) != std::string_view::npos) { static const std::regex ORIENTATION_REGEX("device_orientation=([-+]?[0-9]+)"); @@ -1191,7 +1208,7 @@ handleMediaControl(SIPCall& call, pjsip_msg_body* body) rotation -= 360; JAMI_WARN("Rotate video %d deg.", rotation); #ifdef ENABLE_VIDEO - call.setRotation(rotation); + call.setRotation(streamIdx, rotation); #endif } catch (const std::exception& e) { JAMI_WARN("Error parsing angle: %s", e.what()); diff --git a/src/sip/sipvoiplink.h b/src/sip/sipvoiplink.h index b6e1beaaa7bfe4e11af921165f0afebde2f6ebae..f44bcc3533544e9da0862fad696f859a5c26e88a 100644 --- a/src/sip/sipvoiplink.h +++ b/src/sip/sipvoiplink.h @@ -165,10 +165,6 @@ private: std::atomic_bool running_ {true}; std::thread sipThread_; -#ifdef ENABLE_VIDEO - void requestKeyframe(const std::string& callID); -#endif - friend class SIPTest; };