diff --git a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java index 27a7fd32..996daf68 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java +++ b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java @@ -374,13 +374,15 @@ public class KurentoSessionManager extends SessionManager { boolean isTranscodingAllowed = kSession.getSessionProperties().isTranscodingAllowed(); VideoCodec forcedVideoCodec = kSession.getSessionProperties().forcedVideoCodec(); + log.debug("PARTICIPANT '{}' in Session '{}' publishing SDP before munging: \n {}", + participant.getParticipantPublicId(), kSession.getSessionId(), kurentoOptions.sdpOffer); // Modify sdp if forced codec is defined if (forcedVideoCodec != VideoCodec.NONE) { String sdpOffer = kurentoOptions.sdpOffer; try { - log.debug("PARTICIPANT '{}' in Session '{}' SDP Offer before munging: \n {}", - participant.getParticipantPublicId(), kSession.getSessionId(), kurentoOptions.sdpOffer); kurentoOptions.sdpOffer = this.sdpMunging.setCodecPreference(forcedVideoCodec, sdpOffer); + log.debug("PARTICIPANT '{}' in Session '{}' publishing SDP Offer after munging: \n {}", + participant.getParticipantPublicId(), kSession.getSessionId(), kurentoOptions.sdpOffer); } catch (OpenViduException e) { String errorMessage = "Error forcing codec: '" + forcedVideoCodec + "', for PARTICIPANT" + participant.getParticipantPublicId() + "' publishing in Session: '" @@ -535,8 +537,6 @@ public class KurentoSessionManager extends SessionManager { String sdpOffer = null; Session session = null; try { - log.debug("Request [SUBSCRIBE] remoteParticipant={} sdpOffer={} ({})", senderPublicId, sdpOffer, - participant.getParticipantPublicId()); KurentoParticipant kParticipant = (KurentoParticipant) participant; session = ((KurentoParticipant) participant).getSession(); @@ -564,6 +564,32 @@ public class KurentoSessionManager extends SessionManager { } sdpOffer = kParticipant.prepareReceiveMediaFrom(senderParticipant); + VideoCodec forcedVideoCodec = session.getSessionProperties().forcedVideoCodec(); + boolean isTranscodingAllowed = session.getSessionProperties().isTranscodingAllowed(); + + if (forcedVideoCodec != VideoCodec.NONE) { + try { + log.debug("PARTICIPANT '{}' in Session '{}' SDP Offer before munging: \n {}", + participant.getParticipantPublicId(), session.getSessionId(), sdpOffer); + sdpOffer = this.sdpMunging.setCodecPreference(forcedVideoCodec, sdpOffer); + if (forcedVideoCodec == VideoCodec.H264) { + sdpOffer = this.sdpMunging.setfmtpH264(sdpOffer); + } + } catch (OpenViduException e) { + String errorMessage = "Error forcing codec: '" + forcedVideoCodec + "', for PARTICIPANT: '" + + participant.getParticipantPublicId() + "' subscribing in Session: '" + + session.getSessionId() + "'\nException: " + e.getMessage() + "\nSDP:\n" + sdpOffer; + + if(!isTranscodingAllowed) { + throw new OpenViduException(Code.FORCED_CODEC_NOT_FOUND_IN_SDPOFFER, errorMessage); + } + log.info("Codec: '" + forcedVideoCodec + "' is not supported for PARTICIPANT: '" + participant.getParticipantPublicId() + + " subscribing in Session: '" + session.getSessionId() + "'. Transcoding will be allowed"); + } + } + log.debug("Request [SUBSCRIBE] remoteParticipant={} sdpOffer={} ({})", senderPublicId, sdpOffer, + participant.getParticipantPublicId()); + if (sdpOffer == null) { throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE, "Unable to generate SDP offer when subscribing '" + participant.getParticipantPublicId() + "' to '" + senderPublicId + "'"); @@ -591,12 +617,14 @@ public class KurentoSessionManager extends SessionManager { boolean isTranscodingAllowed = session.getSessionProperties().isTranscodingAllowed(); VideoCodec forcedVideoCodec = session.getSessionProperties().forcedVideoCodec(); + log.debug("PARTICIPANT '{}' subscribing in Session '{}' SDP Answer before munging: \n {}", + participant.getParticipantPublicId(), session.getSessionId(), sdpAnswer); // Modify sdp if forced codec is defined if (forcedVideoCodec != VideoCodec.NONE) { try { - log.debug("PARTICIPANT '{}' in Session '{}' SDP Answer before munging: \n {}", - participant.getParticipantPublicId(), session.getSessionId(), sdpAnswer); - sdpAnswer = this.sdpMunging.setCodecPreference(forcedVideoCodec, sdpAnswer); + sdpAnswer = this.sdpMunging.setCodecPreference(forcedVideoCodec, sdpAnswer); + log.debug("PARTICIPANT '{}' subscribing in Session '{}' SDP Answer after munging: \n {}", + participant.getParticipantPublicId(), session.getSessionId(), sdpAnswer); } catch (OpenViduException e) { String errorMessage = "Error forcing codec: '" + forcedVideoCodec + "', for PARTICIPANT: '" + participant.getParticipantPublicId() + "' subscribing in Session: '" @@ -609,6 +637,7 @@ public class KurentoSessionManager extends SessionManager { + " subscribing in Session: '" + session.getSessionId() + "'. Transcoding will be allowed"); } } + if (senderParticipant == null) { log.warn( @@ -1144,12 +1173,14 @@ public class KurentoSessionManager extends SessionManager { boolean isTranscodingAllowed = kSession.getSessionProperties().isTranscodingAllowed(); VideoCodec forcedVideoCodec = kSession.getSessionProperties().forcedVideoCodec(); + log.debug("PARTICIPANT '{}' in Session '{}' reconnecting SDP before munging: \n {}", + participant.getParticipantPublicId(), kSession.getSessionId(), sdpString); // Modify sdp if forced codec is defined if (forcedVideoCodec != VideoCodec.NONE) { try { - log.debug("PARTICIPANT '{}' in Session '{}' reconnecting SDP before munging: \n {}", - participant.getParticipantPublicId(), kSession.getSessionId(), sdpString); sdpString = sdpMunging.setCodecPreference(forcedVideoCodec, sdpString); + log.debug("PARTICIPANT '{}' in Session '{}' reconnecting SDP after munging: \n {}", + participant.getParticipantPublicId(), kSession.getSessionId(), sdpString); } catch (OpenViduException e) { String errorMessage = "Error in reconnect and forcing codec: '" + forcedVideoCodec + "', for PARTICIPANT: '" + participant.getParticipantPublicId() + "' " + (isPublisher ? "publishing" : "subscribing") diff --git a/openvidu-server/src/main/java/io/openvidu/server/utils/SDPMunging.java b/openvidu-server/src/main/java/io/openvidu/server/utils/SDPMunging.java index 8537c39e..0a705481 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/utils/SDPMunging.java +++ b/openvidu-server/src/main/java/io/openvidu/server/utils/SDPMunging.java @@ -2,6 +2,7 @@ package io.openvidu.server.utils; import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -127,7 +128,78 @@ public class SDPMunging { lines[sl] = newLine.toString(); } - return String.join("\r\n", lines); + return String.join("\r\n", lines) + "\r\n"; } - + + /** + * Possible Kurento's bug + * Some browsers can't use H264 as a video codec if in the offerer SDP + * the parameter "a=fmtp: <...> profile-level-id=42e01f" is not defined. + * This munging is only used when the forced codec needs to be H264 + * References: + * https://stackoverflow.com/questions/38432137/cannot-establish-webrtc-connection-different-codecs-and-payload-type-in-sdp + */ + public String setfmtpH264(String sdp) { + String codecStr = VideoCodec.H264.name(); + + // Get all lines + List lines = new LinkedList(Arrays.asList(sdp.split("\\R+"))); + + // Index to reference the line with "m=video" + int mVideoLineIndex = -1; + List validCodecsPayload = new ArrayList<>(); + + for(int i = 0; i < lines.size(); i++) { + String sdpLine = lines.get(i); + + // Check that we are in "m=video" + if (sdpLine.startsWith("m=video")) { + mVideoLineIndex = i; + + // Search for payload-type for the specified codec + for(int j = i+1; j < lines.size(); j++) { + String auxLine = lines.get(j); + + // Check that the line we're going to analizae is not anoter "m=" + if(auxLine.startsWith("m=")) { + break; + } + + if (auxLine.startsWith("a=rtpmap")) { + String[] rtpmapInfo = auxLine.split(":")[1].split(" "); + String possiblePayload = rtpmapInfo[0]; + String possibleCodec = rtpmapInfo[1]; + if (possibleCodec.contains(codecStr)) { + validCodecsPayload.add(possiblePayload); + } + } + } + // If a payload is not found, then the codec is not in the SDP + if (validCodecsPayload.size() == 0) { + throw new OpenViduException(Code.FORCED_CODEC_NOT_FOUND_IN_SDPOFFER, String.format("Codec %s not found", codecStr)); + } + continue; + } + + } + if (mVideoLineIndex == -1) { + throw new OpenViduException(Code.FORCED_CODEC_NOT_FOUND_IN_SDPOFFER, "This SDP does not offer video"); + } + + if (mVideoLineIndex != -1) { + for (String codecPayload: validCodecsPayload) { + if (!sdp.contains(String.format("a=fmtp:%s", codecPayload))) { + String newfmtpLine = String.format("a=fmtp:%s level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", codecPayload); + lines.add(mVideoLineIndex + 1, newfmtpLine); + } + } + + } + + // Return munging sdp!! + String[] munguedSdpLines = lines.toArray(new String[lines.size()]); + return String.join("\r\n", munguedSdpLines) + "\r\n"; + + } + } \ No newline at end of file diff --git a/openvidu-server/src/test/java/io/openvidu/server/test/unit/SDPMungingTest.java b/openvidu-server/src/test/java/io/openvidu/server/test/unit/SDPMungingTest.java new file mode 100644 index 00000000..a60bd398 --- /dev/null +++ b/openvidu-server/src/test/java/io/openvidu/server/test/unit/SDPMungingTest.java @@ -0,0 +1,253 @@ +package io.openvidu.server.test.unit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.junit.jupiter.api.DisplayName; + +import io.openvidu.client.OpenViduException; +import io.openvidu.java.client.VideoCodec; +import io.openvidu.server.utils.SDPMunging; + +public class SDPMungingTest { + + private SDPMunging sdpMungin = new SDPMunging(); + + private String oldSdp; + + private String newSdp; + + List h264codecPayloads; + + List forceCodecPayloads; + + String validSDPH264Files[] = new String[]{ + "sdp_kurento_h264_with_profile_id.txt", + "sdp_kurento_h264_without_profile_id_1.txt", + "sdp_chrome84.txt", + "sdp_firefox79.txt", + "sdp_safari13-1.txt" + }; + + String validSDPVP8Files[] = new String[]{ + "sdp_kurento_h264_with_profile_id.txt", + "sdp_kurento_h264_without_profile_id_1.txt", + "sdp_chrome84.txt", + "sdp_firefox79.txt", + "sdp_safari13-1.txt" + }; + + String validSDPVP9Files[] = new String[] { + "sdp_chrome84.txt", + "sdp_firefox79.txt" + }; + + String notValidVP9Files[] = new String[] { + "sdp_kurento_h264_with_profile_id.txt", + "sdp_kurento_h264_without_profile_id_1.txt", + "sdp_safari13-1.txt" + }; + + @Test + @DisplayName("[setfmtpH264] Compare 'profile-level-id' ocurrences with Payload-Type H264 ocurrences") + public void checkSDPOcurrenciesOfProfileIdForH264() throws IOException { + for(String sdpFileName: validSDPH264Files) { + initTestsSetfmtp(sdpFileName); + checkOcurrences(); + } + } + + @Test + @DisplayName("[setfmtpH264] In the generated SDP for each H264 Payload-Type, exists one" + + "'a=fmtp:' with a 'profile-level-id' defined") + public void checkOneProfileIdForEachH264Payload() throws IOException { + for(String sdpFileName: validSDPH264Files) { + initTestsSetfmtp(sdpFileName); + checkOneProfileIdForEachH264(); + } + } + + @Test + @DisplayName("[setCodecPreference] Force VP8 Codec prevalence in 'm=video' line") + public void checkPreferenceCodecVP8() throws IOException { + for(String sdpFileName: validSDPVP8Files) { + initTestsSetCodecPrevalence(VideoCodec.VP8, sdpFileName); + checkPrevalenceCodecInML(); + } + } + + @Test + @DisplayName("[setCodecPreference] Force VP8 Codec prevalence in 'm=video' line") + public void checkPreferenceCodecVP9() throws IOException { + for(String sdpFileName: validSDPVP9Files) { + initTestsSetCodecPrevalence(VideoCodec.VP9, sdpFileName); + checkPrevalenceCodecInML(); + } + } + + @Test + @DisplayName("[setCodecPreference] Force H264 Codec prevalence in 'm=video' line") + public void checkPreferenceCodecH264() throws IOException { + for(String sdpFileName: validSDPH264Files) { + initTestsSetCodecPrevalence(VideoCodec.H264, sdpFileName); + checkPrevalenceCodecInML(); + } + } + + @Test + @DisplayName("[setCodecPreference] Exception when codec does not exists on SDP") + public void checkPreferenceCodecException() throws IOException { + for(String sdpFile: notValidVP9Files) { + Exception exception = assertThrows(OpenViduException.class, () -> { + initTestsSetCodecPrevalence(VideoCodec.VP9, sdpFile); + }); + String expectedMessage = "The specified forced codec VP9 is not present in the SDP"; + assertTrue(exception.getMessage().contains(expectedMessage)); + } + } + + private String getSdpFile(String sdpNameFile) throws IOException { + Path sdpFile = Files.createTempFile("sdp-test", ".tmp"); + Files.copy(getClass().getResourceAsStream("/sdp/" + sdpNameFile), sdpFile, StandardCopyOption.REPLACE_EXISTING); + String sdpUnformatted = new String(Files.readAllBytes(sdpFile)); + return String.join("\r\n", sdpUnformatted.split("\\R+")) + "\r\n"; + } + + private void checkOcurrences() { + // Check that new sdp actually contains a line with a=fmtp for + // each h264 defined payload + int allOcurrences = 0; + for(String h264pt: this.h264codecPayloads) { + allOcurrences += ( newSdp.split("m=video")[1].split("a=fmtp:" + h264pt).length ) - 1; + } + assertEquals(allOcurrences, h264codecPayloads.size()); + } + + private void initTestsSetfmtp(String sdpNameFile) throws IOException { + this.oldSdp = getSdpFile(sdpNameFile); + this.newSdp = this.sdpMungin.setfmtpH264(oldSdp); + this.h264codecPayloads = new ArrayList<>(); + + // Get all Payload-Type for h264 + for(String oldSdpLine: oldSdp.split("\\R+")) { + if(oldSdpLine.startsWith("a=rtpmap") && oldSdpLine.endsWith("H264/90000")) { + String pt = oldSdpLine.split(":")[1].split(" ")[0]; + this.h264codecPayloads.add(pt); + } + } + } + + private void initTestsSetCodecPrevalence(VideoCodec codec, String sdpNameFile) throws IOException { + this.oldSdp = getSdpFile(sdpNameFile); + this.newSdp = this.sdpMungin.setCodecPreference(codec, oldSdp); + this.forceCodecPayloads = new ArrayList<>(); + + // Get all Payload-Type for video Codec + for(String oldSdpLine: oldSdp.split("\\R+")) { + if(oldSdpLine.startsWith("a=rtpmap") && oldSdpLine.endsWith(codec.name() + "/90000")) { + String pt = oldSdpLine.split(":")[1].split(" ")[0]; + this.forceCodecPayloads.add(pt); + } + } + + // Get all Payload-Types rtx related with codec + // Not the best way to do it, but enough to check if the sdp + // generated is correct + String[] oldSdpLines = oldSdp.split("\\R+"); + List rtxForcedCodecs = new ArrayList<>(); + for(String oldSdpLine: oldSdpLines) { + if(oldSdpLine.startsWith("a=rtpmap") && oldSdpLine.endsWith("rtx/90000")) { + String rtxPayload = oldSdpLine.split(":")[1].split(" ")[0]; + for (String auxOldSdpLine: oldSdpLines) { + if (auxOldSdpLine.contains("a=fmtp:" + rtxPayload + " apt=")) { + for (String auxForcedCodec: this.forceCodecPayloads) { + if (auxOldSdpLine.contains("a=fmtp:" + rtxPayload + " apt=" + auxForcedCodec)) { + String pt = oldSdpLine.split(":")[1].split(" ")[0]; + rtxForcedCodecs.add(pt); + } + } + } + } + } + } + this.forceCodecPayloads.addAll(rtxForcedCodecs); + } + + private void checkOneProfileIdForEachH264() throws IOException { + + // Check one profile-id for each h264 Payload-Type + boolean inVideoBlock = false; + int numFoundProfileIds = 0; + for(String newSdpLine: newSdp.split("\\R+")) { + if (!inVideoBlock && newSdpLine.startsWith("m=video")) { + inVideoBlock = true; + continue; + } + + if (inVideoBlock && newSdpLine.startsWith("m=")) { + break; + } + + if (inVideoBlock && newSdpLine.startsWith("a=fmtp:")) { + boolean foundProfileId = false; + for(String h264pt: this.h264codecPayloads) { + foundProfileId = newSdpLine.contains("a=fmtp:") + && newSdpLine.contains(h264pt) && newSdpLine.contains("profile-level-id"); + + if (foundProfileId) { + numFoundProfileIds++; + } + } + } + } + assertEquals(numFoundProfileIds, this.h264codecPayloads.size()); + } + + private void checkPrevalenceCodecInML() { + + String newml = null; + String[] newSdpLines = this.newSdp.split("\\R+"); + for(String newSdpLine: newSdpLines) { + if (newSdpLine.startsWith("m=video")) { + newml = newSdpLine; + break; + } + } + + if (newml == null) { + fail("'m=video' line not found in SDP"); + } + + List newMlCodecPrevalenceList = new ArrayList<>(); + String[] lmParams = newml.split(" "); + int numOfCodecsWithPrevalence = this.forceCodecPayloads.size(); + int indexStartCodecs = 3; + int indexEndPreferencedCodecs = 3 + numOfCodecsWithPrevalence; + for(int i = indexStartCodecs; i < indexEndPreferencedCodecs; i++) { + newMlCodecPrevalenceList.add(lmParams[i]); + } + + for(int j = 0; j < numOfCodecsWithPrevalence; j++) { + String codecToCheck = newMlCodecPrevalenceList.get(j); + boolean codecFoundInPrevalenceList = false; + for(String codecToForce: this.forceCodecPayloads) { + if (codecToCheck.equals(codecToForce)) { + codecFoundInPrevalenceList = true; + break; + } + } + assertTrue(codecFoundInPrevalenceList); + } + } + +} diff --git a/openvidu-server/src/test/resources/sdp/sdp_chrome84.txt b/openvidu-server/src/test/resources/sdp/sdp_chrome84.txt new file mode 100644 index 00000000..d2ea6903 --- /dev/null +++ b/openvidu-server/src/test/resources/sdp/sdp_chrome84.txt @@ -0,0 +1,143 @@ +v=0 +o=- 5217540180782877494 2 IN IP4 127.0.0.1 +s=- +t=0 0 +a=group:BUNDLE 0 1 +a=msid-semantic: WMS eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul +m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:RWim +a=ice-pwd:seCgmyE+AkRJKgqD4SdIuALd +a=ice-options:trickle +a=fingerprint:sha-256 59:47:FE:36:82:34:B1:7B:4C:D5:D4:76:78:24:89:67:5E:3C:84:4F:52:BD:86:83:67:10:98:8C:79:9D:89:7D +a=setup:actpass +a=mid:0 +a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level +a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 +a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid +a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id +a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id +a=sendonly +a=msid:eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul 7bbc37fe-6e36-479f-990f-988a07ac3f00 +a=rtcp-mux +a=rtpmap:111 opus/48000/2 +a=rtcp-fb:111 transport-cc +a=fmtp:111 minptime=10;useinbandfec=1 +a=rtpmap:103 ISAC/16000 +a=rtpmap:104 ISAC/32000 +a=rtpmap:9 G722/8000 +a=rtpmap:0 PCMU/8000 +a=rtpmap:8 PCMA/8000 +a=rtpmap:106 CN/32000 +a=rtpmap:105 CN/16000 +a=rtpmap:13 CN/8000 +a=rtpmap:110 telephone-event/48000 +a=rtpmap:112 telephone-event/32000 +a=rtpmap:113 telephone-event/16000 +a=rtpmap:126 telephone-event/8000 +a=ssrc:913601848 cname:BkItF+kVUhq9L3k4 +a=ssrc:913601848 msid:eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul 7bbc37fe-6e36-479f-990f-988a07ac3f00 +a=ssrc:913601848 mslabel:eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul +a=ssrc:913601848 label:7bbc37fe-6e36-479f-990f-988a07ac3f00 +m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 122 127 121 125 107 108 109 124 120 123 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:RWim +a=ice-pwd:seCgmyE+AkRJKgqD4SdIuALd +a=ice-options:trickle +a=fingerprint:sha-256 59:47:FE:36:82:34:B1:7B:4C:D5:D4:76:78:24:89:67:5E:3C:84:4F:52:BD:86:83:67:10:98:8C:79:9D:89:7D +a=setup:actpass +a=mid:1 +a=extmap:14 urn:ietf:params:rtp-hdrext:toffset +a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=extmap:13 urn:3gpp:video-orientation +a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 +a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay +a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type +a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing +a=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07 +a=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space +a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid +a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id +a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id +a=sendonly +a=msid:eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul 29491954-cc1e-4ca3-b290-cb8d5d0ad685 +a=rtcp-mux +a=rtcp-rsize +a=rtpmap:96 VP8/90000 +a=rtcp-fb:96 goog-remb +a=rtcp-fb:96 transport-cc +a=rtcp-fb:96 ccm fir +a=rtcp-fb:96 nack +a=rtcp-fb:96 nack pli +a=rtpmap:97 rtx/90000 +a=fmtp:97 apt=96 +a=rtpmap:98 VP9/90000 +a=rtcp-fb:98 goog-remb +a=rtcp-fb:98 transport-cc +a=rtcp-fb:98 ccm fir +a=rtcp-fb:98 nack +a=rtcp-fb:98 nack pli +a=fmtp:98 profile-id=0 +a=rtpmap:99 rtx/90000 +a=fmtp:99 apt=98 +a=rtpmap:100 VP9/90000 +a=rtcp-fb:100 goog-remb +a=rtcp-fb:100 transport-cc +a=rtcp-fb:100 ccm fir +a=rtcp-fb:100 nack +a=rtcp-fb:100 nack pli +a=fmtp:100 profile-id=2 +a=rtpmap:101 rtx/90000 +a=fmtp:101 apt=100 +a=rtpmap:102 H264/90000 +a=rtcp-fb:102 goog-remb +a=rtcp-fb:102 transport-cc +a=rtcp-fb:102 ccm fir +a=rtcp-fb:102 nack +a=rtcp-fb:102 nack pli +a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f +a=rtpmap:122 rtx/90000 +a=fmtp:122 apt=102 +a=rtpmap:127 H264/90000 +a=rtcp-fb:127 goog-remb +a=rtcp-fb:127 transport-cc +a=rtcp-fb:127 ccm fir +a=rtcp-fb:127 nack +a=rtcp-fb:127 nack pli +a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f +a=rtpmap:121 rtx/90000 +a=fmtp:121 apt=127 +a=rtpmap:125 H264/90000 +a=rtcp-fb:125 goog-remb +a=rtcp-fb:125 transport-cc +a=rtcp-fb:125 ccm fir +a=rtcp-fb:125 nack +a=rtcp-fb:125 nack pli +a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f +a=rtpmap:107 rtx/90000 +a=fmtp:107 apt=125 +a=rtpmap:108 H264/90000 +a=rtcp-fb:108 goog-remb +a=rtcp-fb:108 transport-cc +a=rtcp-fb:108 ccm fir +a=rtcp-fb:108 nack +a=rtcp-fb:108 nack pli +a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f +a=rtpmap:109 rtx/90000 +a=fmtp:109 apt=108 +a=rtpmap:124 red/90000 +a=rtpmap:120 rtx/90000 +a=fmtp:120 apt=124 +a=rtpmap:123 ulpfec/90000 +a=ssrc-group:FID 796117241 4235816742 +a=ssrc:796117241 cname:BkItF+kVUhq9L3k4 +a=ssrc:796117241 msid:eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul 29491954-cc1e-4ca3-b290-cb8d5d0ad685 +a=ssrc:796117241 mslabel:eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul +a=ssrc:796117241 label:29491954-cc1e-4ca3-b290-cb8d5d0ad685 +a=ssrc:4235816742 cname:BkItF+kVUhq9L3k4 +a=ssrc:4235816742 msid:eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul 29491954-cc1e-4ca3-b290-cb8d5d0ad685 +a=ssrc:4235816742 mslabel:eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul +a=ssrc:4235816742 label:29491954-cc1e-4ca3-b290-cb8d5d0ad685 diff --git a/openvidu-server/src/test/resources/sdp/sdp_firefox79.txt b/openvidu-server/src/test/resources/sdp/sdp_firefox79.txt new file mode 100644 index 00000000..b2918cf7 --- /dev/null +++ b/openvidu-server/src/test/resources/sdp/sdp_firefox79.txt @@ -0,0 +1,67 @@ +v=0 +o=mozilla...THIS_IS_SDPARTA-79.0 1574413241511582424 0 IN IP4 0.0.0.0 +s=- +t=0 0 +a=sendrecv +a=fingerprint:sha-256 43:63:A0:1A:D4:F3:6A:0B:B9:DC:AD:8B:A4:20:22:17:B9:BD:FC:81:9F:EC:E9:46:E0:61:3B:8B:2A:05:9A:D9 +a=group:BUNDLE 0 1 +a=ice-options:trickle +a=msid-semantic:WMS * +m=audio 9 UDP/TLS/RTP/SAVPF 109 9 0 8 101 +c=IN IP4 0.0.0.0 +a=sendonly +a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level +a=extmap:2/recvonly urn:ietf:params:rtp-hdrext:csrc-audio-level +a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid +a=fmtp:109 maxplaybackrate=48000;stereo=1;useinbandfec=1 +a=fmtp:101 0-15 +a=ice-pwd:4be109f1ce637a5423d9229aa10c43bd +a=ice-ufrag:03567d22 +a=mid:0 +a=msid:{77708aaa-ed09-403a-b19a-26b8f44aff96} {f18baa5e-b689-4b17-9db8-83620b6c1f2b} +a=rtcp-mux +a=rtpmap:109 opus/48000/2 +a=rtpmap:9 G722/8000/1 +a=rtpmap:0 PCMU/8000 +a=rtpmap:8 PCMA/8000 +a=rtpmap:101 telephone-event/8000 +a=setup:actpass +a=ssrc:2626852385 cname:{4ff79710-1727-45f8-bbe7-7532884b9652} +m=video 9 UDP/TLS/RTP/SAVPF 120 121 126 97 +c=IN IP4 0.0.0.0 +a=sendonly +a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid +a=extmap:4 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=extmap:5 urn:ietf:params:rtp-hdrext:toffset +a=extmap:6/recvonly http://www.webrtc.org/experiments/rtp-hdrext/playout-delay +a=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1 +a=fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1 +a=fmtp:120 max-fs=12288;max-fr=60 +a=fmtp:121 max-fs=12288;max-fr=60 +a=ice-pwd:4be109f1ce637a5423d9229aa10c43bd +a=ice-ufrag:03567d22 +a=mid:1 +a=msid:{77708aaa-ed09-403a-b19a-26b8f44aff96} {a68009be-6483-45fd-a48d-7bc60fa6f055} +a=rtcp-fb:120 nack +a=rtcp-fb:120 nack pli +a=rtcp-fb:120 ccm fir +a=rtcp-fb:120 goog-remb +a=rtcp-fb:121 nack +a=rtcp-fb:121 nack pli +a=rtcp-fb:121 ccm fir +a=rtcp-fb:121 goog-remb +a=rtcp-fb:126 nack +a=rtcp-fb:126 nack pli +a=rtcp-fb:126 ccm fir +a=rtcp-fb:126 goog-remb +a=rtcp-fb:97 nack +a=rtcp-fb:97 nack pli +a=rtcp-fb:97 ccm fir +a=rtcp-fb:97 goog-remb +a=rtcp-mux +a=rtpmap:120 VP8/90000 +a=rtpmap:121 VP9/90000 +a=rtpmap:126 H264/90000 +a=rtpmap:97 H264/90000 +a=setup:actpass +a=ssrc:3743317987 cname:{4ff79710-1727-45f8-bbe7-7532884b9652} \ No newline at end of file diff --git a/openvidu-server/src/test/resources/sdp/sdp_kurento_h264_with_profile_id.txt b/openvidu-server/src/test/resources/sdp/sdp_kurento_h264_with_profile_id.txt new file mode 100644 index 00000000..b21ca1a0 --- /dev/null +++ b/openvidu-server/src/test/resources/sdp/sdp_kurento_h264_with_profile_id.txt @@ -0,0 +1,61 @@ +v=0 +o=- 3808465464 3808465464 IN IP4 0.0.0.0 +s=Kurento Media Server +c=IN IP4 0.0.0.0 +t=0 0 +a=msid-semantic: WMS m0W2gMak7LgkgzgJeDQhxBX0ivcsejWjQ0jD +a=group:BUNDLE 0 1 +m=audio 1 UDP/TLS/RTP/SAVPF 111 0 +a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=recvonly +a=mid:0 +a=rtcp:9 IN IP4 0.0.0.0 +a=rtpmap:111 opus/48000/2 +a=rtpmap:0 PCMU/8000 +a=setup:active +a=rtcp-mux +a=fmtp:111 minptime=10;useinbandfec=1 +a=ssrc:1929271881 cname:user4129876135@host-ed881df6 +a=ice-ufrag:cXmf +a=ice-pwd:9giZcfpsuoHRuxCgbnCLRy +a=fingerprint:sha-256 C8:D4:B5:56:A7:89:E5:E1:C8:28:0A:47:2B:49:F6:7A:E2:2E:B3:0A:40:10:AD:79:82:E7:FD:A0:ED:6C:F6:51 +m=video 1 UDP/TLS/RTP/SAVPF 96 102 127 125 108 +a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=recvonly +a=mid:1 +a=rtcp:9 IN IP4 0.0.0.0 +a=rtpmap:96 VP8/90000 +a=rtpmap:102 H264/90000 +a=rtpmap:127 H264/90000 +a=rtpmap:125 H264/90000 +a=rtpmap:108 H264/90000 +a=rtcp-fb:96 goog-remb +a=rtcp-fb:96 ccm fir +a=rtcp-fb:96 nack +a=rtcp-fb:96 nack pli +a=rtcp-fb:102 goog-remb +a=rtcp-fb:102 ccm fir +a=rtcp-fb:102 nack +a=rtcp-fb:102 nack pli +a=rtcp-fb:127 goog-remb +a=rtcp-fb:127 ccm fir +a=rtcp-fb:127 nack +a=rtcp-fb:127 nack pli +a=rtcp-fb:125 goog-remb +a=rtcp-fb:125 ccm fir +a=rtcp-fb:125 nack +a=rtcp-fb:125 nack pli +a=rtcp-fb:108 goog-remb +a=rtcp-fb:108 ccm fir +a=rtcp-fb:108 nack +a=rtcp-fb:108 nack pli +a=setup:active +a=rtcp-mux +a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f +a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f +a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f +a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f +a=ssrc:3762875210 cname:user4129876135@host-ed881df6 +a=ice-ufrag:cXmf +a=ice-pwd:9giZcfpsuoHRuxCgbnCLRy +a=fingerprint:sha-256 C8:D4:B5:56:A7:89:E5:E1:C8:28:0A:47:2B:49:F6:7A:E2:2E:B3:0A:40:10:AD:79:82:E7:FD:A0:ED:6C:F6:51 diff --git a/openvidu-server/src/test/resources/sdp/sdp_kurento_h264_without_profile_id_1.txt b/openvidu-server/src/test/resources/sdp/sdp_kurento_h264_without_profile_id_1.txt new file mode 100644 index 00000000..d611aa86 --- /dev/null +++ b/openvidu-server/src/test/resources/sdp/sdp_kurento_h264_without_profile_id_1.txt @@ -0,0 +1,39 @@ +v=0 +o=- 3808468344 3808468344 IN IP4 0.0.0.0 +s=Kurento Media Server +c=IN IP4 0.0.0.0 +t=0 0 +a=group:BUNDLE audio0 video0 +m=audio 1 RTP/SAVPF 96 0 +a=setup:actpass +a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=rtpmap:96 opus/48000/2 +a=rtcp:9 IN IP4 0.0.0.0 +a=rtcp-mux +a=sendrecv +a=mid:audio0 +a=ssrc:2357386425 cname:user3039051834@host-3860d219 +a=ice-ufrag:cbwA +a=ice-pwd:sgFJgRt8te7axq+VVp7L9S +a=fingerprint:sha-256 C8:D4:B5:56:A7:89:E5:E1:C8:28:0A:47:2B:49:F6:7A:E2:2E:B3:0A:40:10:AD:79:82:E7:FD:A0:ED:6C:F6:51 +m=video 1 RTP/SAVPF 100 101 +b=AS:1000 +a=setup:actpass +a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=rtpmap:100 VP8/90000 +a=rtpmap:101 H264/90000 +a=rtcp:9 IN IP4 0.0.0.0 +a=rtcp-mux +a=sendrecv +a=mid:video0 +a=rtcp-fb:100 nack +a=rtcp-fb:100 nack pli +a=rtcp-fb:100 goog-remb +a=rtcp-fb:100 ccm fir +a=rtcp-fb:101 nack +a=rtcp-fb:101 nack pli +a=rtcp-fb:101 ccm fir +a=ssrc:728841848 cname:user3039051834@host-3860d219 +a=ice-ufrag:cbwA +a=ice-pwd:sgFJgRt8te7axq+VVp7L9S +a=fingerprint:sha-256 C8:D4:B5:56:A7:89:E5:E1:C8:28:0A:47:2B:49:F6:7A:E2:2E:B3:0A:40:10:AD:79:82:E7:FD:A0:ED:6C:F6:51 \ No newline at end of file diff --git a/openvidu-server/src/test/resources/sdp/sdp_safari13-1.txt b/openvidu-server/src/test/resources/sdp/sdp_safari13-1.txt new file mode 100644 index 00000000..e0292781 --- /dev/null +++ b/openvidu-server/src/test/resources/sdp/sdp_safari13-1.txt @@ -0,0 +1,107 @@ +v=0 +o=- 4920969914039852086 2 IN IP4 127.0.0.1 +s=- +t=0 0 +a=group:BUNDLE 0 1 +a=msid-semantic: WMS 90c21009-8d9f-4b31-8091-d98deb8361c8 +m=audio 61842 UDP/TLS/RTP/SAVPF 111 103 9 102 0 8 105 13 110 113 126 +c=IN IP4 192.168.1.105 +a=rtcp:9 IN IP4 0.0.0.0 +a=candidate:2222700650 1 udp 2113937151 192.168.1.105 61842 typ host generation 0 network-cost 999 +a=ice-ufrag:zbK7 +a=ice-pwd:o4ZsJFGBXHNzOWqX23brKpiG +a=ice-options:trickle +a=fingerprint:sha-256 18:BD:1F:64:28:C6:BC:7B:AD:83:42:E0:B1:78:BA:13:F4:BF:F5:5E:AD:20:62:CC:AF:DF:99:AD:20:CB:1B:7E +a=setup:actpass +a=mid:0 +a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level +a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 +a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid +a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id +a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id +a=sendonly +a=msid:90c21009-8d9f-4b31-8091-d98deb8361c8 1d7a65f7-59c9-42a7-abbb-d9fb5548e3ba +a=rtcp-mux +a=rtpmap:111 opus/48000/2 +a=rtcp-fb:111 transport-cc +a=fmtp:111 minptime=10;useinbandfec=1 +a=rtpmap:103 ISAC/16000 +a=rtpmap:9 G722/8000 +a=rtpmap:102 ILBC/8000 +a=rtpmap:0 PCMU/8000 +a=rtpmap:8 PCMA/8000 +a=rtpmap:105 CN/16000 +a=rtpmap:13 CN/8000 +a=rtpmap:110 telephone-event/48000 +a=rtpmap:113 telephone-event/16000 +a=rtpmap:126 telephone-event/8000 +a=ssrc:1052596434 cname:L3DqI2bKTDkcNVsn +a=ssrc:1052596434 msid:90c21009-8d9f-4b31-8091-d98deb8361c8 1d7a65f7-59c9-42a7-abbb-d9fb5548e3ba +a=ssrc:1052596434 mslabel:90c21009-8d9f-4b31-8091-d98deb8361c8 +a=ssrc:1052596434 label:1d7a65f7-59c9-42a7-abbb-d9fb5548e3ba +m=video 62559 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 125 104 +c=IN IP4 192.168.1.105 +a=rtcp:9 IN IP4 0.0.0.0 +a=candidate:2222700650 1 udp 2113937151 192.168.1.105 62559 typ host generation 0 network-cost 999 +a=ice-ufrag:zbK7 +a=ice-pwd:o4ZsJFGBXHNzOWqX23brKpiG +a=ice-options:trickle +a=fingerprint:sha-256 18:BD:1F:64:28:C6:BC:7B:AD:83:42:E0:B1:78:BA:13:F4:BF:F5:5E:AD:20:62:CC:AF:DF:99:AD:20:CB:1B:7E +a=setup:actpass +a=mid:1 +a=extmap:14 urn:ietf:params:rtp-hdrext:toffset +a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=extmap:13 urn:3gpp:video-orientation +a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 +a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay +a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type +a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing +a=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07 +a=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space +a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid +a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id +a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id +a=sendonly +a=msid:90c21009-8d9f-4b31-8091-d98deb8361c8 fc45e668-c301-41fe-ae8a-1265a5a355e7 +a=rtcp-mux +a=rtcp-rsize +a=rtpmap:96 H264/90000 +a=rtcp-fb:96 goog-remb +a=rtcp-fb:96 transport-cc +a=rtcp-fb:96 ccm fir +a=rtcp-fb:96 nack +a=rtcp-fb:96 nack pli +a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f +a=rtpmap:97 rtx/90000 +a=fmtp:97 apt=96 +a=rtpmap:98 H264/90000 +a=rtcp-fb:98 goog-remb +a=rtcp-fb:98 transport-cc +a=rtcp-fb:98 ccm fir +a=rtcp-fb:98 nack +a=rtcp-fb:98 nack pli +a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f +a=rtpmap:99 rtx/90000 +a=fmtp:99 apt=98 +a=rtpmap:100 VP8/90000 +a=rtcp-fb:100 goog-remb +a=rtcp-fb:100 transport-cc +a=rtcp-fb:100 ccm fir +a=rtcp-fb:100 nack +a=rtcp-fb:100 nack pli +a=rtpmap:101 rtx/90000 +a=fmtp:101 apt=100 +a=rtpmap:127 red/90000 +a=rtpmap:125 rtx/90000 +a=fmtp:125 apt=127 +a=rtpmap:104 ulpfec/90000 +a=ssrc-group:FID 2734983896 3694891391 +a=ssrc:2734983896 cname:L3DqI2bKTDkcNVsn +a=ssrc:2734983896 msid:90c21009-8d9f-4b31-8091-d98deb8361c8 fc45e668-c301-41fe-ae8a-1265a5a355e7 +a=ssrc:2734983896 mslabel:90c21009-8d9f-4b31-8091-d98deb8361c8 +a=ssrc:2734983896 label:fc45e668-c301-41fe-ae8a-1265a5a355e7 +a=ssrc:3694891391 cname:L3DqI2bKTDkcNVsn +a=ssrc:3694891391 msid:90c21009-8d9f-4b31-8091-d98deb8361c8 fc45e668-c301-41fe-ae8a-1265a5a355e7 +a=ssrc:3694891391 mslabel:90c21009-8d9f-4b31-8091-d98deb8361c8 +a=ssrc:3694891391 label:fc45e668-c301-41fe-ae8a-1265a5a355e7