You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RtxModifier.js 6.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import { getLogger } from '@jitsi/logger';
  2. import { MediaDirection } from '../../service/RTC/MediaDirection';
  3. import { MediaType } from '../../service/RTC/MediaType';
  4. import { SSRC_GROUP_SEMANTICS } from '../../service/RTC/StandardVideoQualitySettings';
  5. import SDPUtil from './SDPUtil';
  6. import { SdpTransformWrap, parseSecondarySSRC } from './SdpTransformUtil';
  7. const logger = getLogger('modules/sdp/RtxModifier');
  8. /**
  9. * Begin helper functions
  10. */
  11. /**
  12. * Updates or inserts the appropriate rtx information for primarySsrc with
  13. * the given rtxSsrc. If no rtx ssrc for primarySsrc currently exists, it will
  14. * add the appropriate ssrc and ssrc group lines. If primarySsrc already has
  15. * an rtx ssrc, the appropriate ssrc and group lines will be updated
  16. * @param {MLineWrap} mLine
  17. * @param {object} primarySsrcInfo the info (ssrc, msid & cname) for the
  18. * primary ssrc
  19. * @param {number} rtxSsrc the rtx ssrc to associate with the primary ssrc
  20. */
  21. function updateAssociatedRtxStream(mLine, primarySsrcInfo, rtxSsrc) {
  22. const primarySsrc = primarySsrcInfo.id;
  23. const primarySsrcMsid = primarySsrcInfo.msid;
  24. const primarySsrcCname = primarySsrcInfo.cname;
  25. const previousRtxSSRC = mLine.getRtxSSRC(primarySsrc);
  26. if (previousRtxSSRC === rtxSsrc) {
  27. return;
  28. }
  29. if (previousRtxSSRC) {
  30. // Stream already had an rtx ssrc that is different than the one given,
  31. // remove all trace of the old one
  32. mLine.removeSSRC(previousRtxSSRC);
  33. mLine.removeGroupsWithSSRC(previousRtxSSRC);
  34. }
  35. mLine.addSSRCAttribute({
  36. id: rtxSsrc,
  37. attribute: 'cname',
  38. value: primarySsrcCname
  39. });
  40. primarySsrcMsid && mLine.addSSRCAttribute({
  41. id: rtxSsrc,
  42. attribute: 'msid',
  43. value: primarySsrcMsid
  44. });
  45. mLine.addSSRCGroup({
  46. semantics: SSRC_GROUP_SEMANTICS.FID,
  47. ssrcs: `${primarySsrc} ${rtxSsrc}`
  48. });
  49. }
  50. /**
  51. * End helper functions
  52. */
  53. /**
  54. * Adds any missing RTX streams for video streams
  55. * and makes sure that they remain consistent
  56. */
  57. export default class RtxModifier {
  58. /**
  59. * Constructor
  60. */
  61. constructor() {
  62. /**
  63. * Map of video ssrc to corresponding RTX
  64. * ssrc
  65. */
  66. this.correspondingRtxSsrcs = new Map();
  67. }
  68. /**
  69. * Clear the cached map of primary video ssrcs to
  70. * their corresponding rtx ssrcs so that they will
  71. * not be used for the next call to modifyRtxSsrcs
  72. */
  73. clearSsrcCache() {
  74. this.correspondingRtxSsrcs.clear();
  75. }
  76. /**
  77. * Explicitly set the primary video ssrc -> rtx ssrc
  78. * mapping to be used in modifyRtxSsrcs
  79. * @param {Map} ssrcMapping a mapping of primary video
  80. * ssrcs to their corresponding rtx ssrcs
  81. */
  82. setSsrcCache(ssrcMapping) {
  83. logger.debug('Setting ssrc cache to ', ssrcMapping);
  84. this.correspondingRtxSsrcs = ssrcMapping;
  85. }
  86. /**
  87. * Adds RTX ssrcs for any video ssrcs that don't already have them. If the video ssrc has been seen before, and
  88. * already had an RTX ssrc generated, the same RTX ssrc will be used again.
  89. *
  90. * @param {string} sdpStr sdp in raw string format
  91. * @returns {string} The modified sdp in raw string format.
  92. */
  93. modifyRtxSsrcs(sdpStr) {
  94. let modified = false;
  95. const sdpTransformer = new SdpTransformWrap(sdpStr);
  96. const videoMLines = sdpTransformer.selectMedia(MediaType.VIDEO);
  97. if (!videoMLines?.length) {
  98. logger.debug(`No 'video' media found in the sdp: ${sdpStr}`);
  99. return sdpStr;
  100. }
  101. for (const videoMLine of videoMLines) {
  102. if (this.modifyRtxSsrcs2(videoMLine)) {
  103. modified = true;
  104. }
  105. }
  106. return modified ? sdpTransformer.toRawSDP() : sdpStr;
  107. }
  108. /**
  109. * Does the same thing as {@link modifyRtxSsrcs}, but takes the {@link MLineWrap} instance wrapping video media as
  110. * an argument.
  111. * @param {MLineWrap} videoMLine
  112. * @return {boolean} <tt>true</tt> if the SDP wrapped by {@link SdpTransformWrap} has been modified or
  113. * <tt>false</tt> otherwise.
  114. */
  115. modifyRtxSsrcs2(videoMLine) {
  116. if (videoMLine.direction === MediaDirection.RECVONLY) {
  117. return false;
  118. }
  119. if (videoMLine.getSSRCCount() < 1) {
  120. return false;
  121. }
  122. const primaryVideoSsrcs = videoMLine.getPrimaryVideoSSRCs();
  123. for (const ssrc of primaryVideoSsrcs) {
  124. const msid = videoMLine.getSSRCAttrValue(ssrc, 'msid');
  125. const cname = videoMLine.getSSRCAttrValue(ssrc, 'cname');
  126. let correspondingRtxSsrc = this.correspondingRtxSsrcs.get(ssrc);
  127. if (!correspondingRtxSsrc) {
  128. // If there's one in the sdp already for it, we'll just set
  129. // that as the corresponding one
  130. const previousAssociatedRtxStream = videoMLine.getRtxSSRC(ssrc);
  131. if (previousAssociatedRtxStream) {
  132. correspondingRtxSsrc = previousAssociatedRtxStream;
  133. } else {
  134. correspondingRtxSsrc = SDPUtil.generateSsrc();
  135. }
  136. this.correspondingRtxSsrcs.set(ssrc, correspondingRtxSsrc);
  137. }
  138. updateAssociatedRtxStream(
  139. videoMLine,
  140. {
  141. id: ssrc,
  142. cname,
  143. msid
  144. },
  145. correspondingRtxSsrc);
  146. }
  147. // FIXME we're not looking into much details whether the SDP has been
  148. // modified or not once the precondition requirements are met.
  149. return true;
  150. }
  151. /**
  152. * Strip all rtx streams from the given sdp.
  153. *
  154. * @param {string} sdpStr sdp in raw string format
  155. * @returns {string} sdp string with all rtx streams stripped
  156. */
  157. stripRtx(sdpStr) {
  158. const sdpTransformer = new SdpTransformWrap(sdpStr);
  159. const videoMLines = sdpTransformer.selectMedia(MediaType.VIDEO);
  160. if (!videoMLines?.length) {
  161. logger.debug(`No 'video' media found in the sdp: ${sdpStr}`);
  162. return sdpStr;
  163. }
  164. for (const videoMLine of videoMLines) {
  165. if (videoMLine.direction !== MediaDirection.RECVONLY
  166. && videoMLine.getSSRCCount()
  167. && videoMLine.containsAnySSRCGroups()) {
  168. const fidGroups = videoMLine.findGroups(SSRC_GROUP_SEMANTICS.FID);
  169. // Remove the fid groups from the mline
  170. videoMLine.removeGroupsBySemantics(SSRC_GROUP_SEMANTICS.FID);
  171. // Get the rtx ssrcs and remove them from the mline
  172. for (const fidGroup of fidGroups) {
  173. const rtxSsrc = parseSecondarySSRC(fidGroup);
  174. videoMLine.removeSSRC(rtxSsrc);
  175. }
  176. }
  177. }
  178. return sdpTransformer.toRawSDP();
  179. }
  180. }