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.spec.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. /* eslint-disable max-len*/
  2. /* jshint maxlen:false*/
  3. import RtxModifier from './RtxModifier.js';
  4. import * as SampleSdpStrings from './SampleSdpStrings.js';
  5. import * as transform from 'sdp-transform';
  6. import * as SDPUtil from './SDPUtil';
  7. /**
  8. * Returns the number of video ssrcs in the given sdp
  9. * @param {object} parsedSdp the sdp as parsed by transform.parse
  10. * @returns {number} the number of video ssrcs in the given sdp
  11. */
  12. function numVideoSsrcs(parsedSdp) {
  13. const videoMLine = parsedSdp.media.find(m => m.type === 'video');
  14. return videoMLine.ssrcs
  15. .map(ssrcInfo => ssrcInfo.id)
  16. .filter((ssrc, index, array) => array.indexOf(ssrc) === index)
  17. .length;
  18. }
  19. /**
  20. * Return the (single) primary video ssrc in the given sdp
  21. * @param {object} parsedSdp the sdp as parsed by transform.parse
  22. * @returns {number} the primary video ssrc in the given sdp
  23. */
  24. function getPrimaryVideoSsrc(parsedSdp) {
  25. const videoMLine = parsedSdp.media.find(m => m.type === 'video');
  26. return parseInt(SDPUtil.parsePrimaryVideoSsrc(videoMLine));
  27. }
  28. /**
  29. * Get the primary video ssrc(s) in the given sdp.
  30. * Only handles parsing 2 scenarios right now:
  31. * 1) Single video ssrc
  32. * 2) Multiple video ssrcs in a single simulcast group
  33. * @param {object} parsedSdp the sdp as parsed by transform.parse
  34. * @returns {list<number>} the primary video ssrcs in the given sdp
  35. */
  36. function getPrimaryVideoSsrcs(parsedSdp) {
  37. const videoMLine = parsedSdp.media.find(m => m.type === 'video');
  38. if (numVideoSsrcs(parsedSdp) === 1) {
  39. return [videoMLine.ssrcs[0].id];
  40. } else {
  41. const simGroups = getVideoGroups(parsedSdp, 'SIM');
  42. if (simGroups.length > 1) {
  43. return;
  44. }
  45. const simGroup = simGroups[0];
  46. return SDPUtil.parseGroupSsrcs(simGroup);
  47. }
  48. }
  49. /**
  50. * Get the video groups that match the passed semantics from the
  51. * given sdp
  52. * @param {object} parsedSDp the sdp as parsed by transform.parse
  53. * @param {string} groupSemantics the semantics string of the groups
  54. * the caller is interested in
  55. * @returns {list<object>} a list of the groups from the given sdp
  56. * that matched the passed semantics
  57. */
  58. function getVideoGroups(parsedSdp, groupSemantics) {
  59. const videoMLine = parsedSdp.media.find(m => m.type === 'video');
  60. videoMLine.ssrcGroups = videoMLine.ssrcGroups || [];
  61. return videoMLine.ssrcGroups
  62. .filter(g => g.semantics === groupSemantics);
  63. }
  64. describe ('RtxModifier', function() {
  65. beforeEach(function() {
  66. this.rtxModifier = new RtxModifier();
  67. this.transform = transform;
  68. this.SDPUtil = SDPUtil;
  69. });
  70. describe ('modifyRtxSsrcs', function() {
  71. describe ('when given an sdp with a single video ssrc', function() {
  72. beforeEach(function() {
  73. this.singleVideoSdp = SampleSdpStrings.plainVideoSdp;
  74. this.primaryVideoSsrc = getPrimaryVideoSsrc(this.singleVideoSdp);
  75. });
  76. it ('should add a single rtx ssrc', function() {
  77. // Call rtxModifier.modifyRtxSsrcs with an sdp that contains a single video
  78. // ssrc. The returned sdp should have an rtx ssrc and an fid group.
  79. const newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.singleVideoSdp));
  80. const newSdp = transform.parse(newSdpStr);
  81. const newPrimaryVideoSsrc = getPrimaryVideoSsrc(newSdp);
  82. expect(newPrimaryVideoSsrc).toEqual(this.primaryVideoSsrc);
  83. // Should now have an rtx ssrc as well
  84. expect(numVideoSsrcs(newSdp)).toEqual(2);
  85. // Should now have an FID group
  86. const fidGroups = getVideoGroups(newSdp, 'FID');
  87. expect(fidGroups.length).toEqual(1);
  88. const fidGroup = fidGroups[0];
  89. const fidGroupPrimarySsrc = SDPUtil.parseGroupSsrcs(fidGroup)[0];
  90. expect(fidGroupPrimarySsrc).toEqual(this.primaryVideoSsrc);
  91. });
  92. it ('should re-use the same rtx ssrc for a primary ssrc it\'s seen before', function() {
  93. // Have rtxModifier generate an rtx ssrc via modifyRtxSsrcs. Then call it again
  94. // with the same primary ssrc in the sdp (but no rtx ssrc). It should use
  95. // the same rtx ssrc as before.
  96. let newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.singleVideoSdp));
  97. let newSdp = transform.parse(newSdpStr);
  98. let fidGroup = getVideoGroups(newSdp, 'FID')[0];
  99. const fidGroupRtxSsrc = SDPUtil.parseGroupSsrcs(fidGroup)[1];
  100. // Now pass the original sdp through again
  101. newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.singleVideoSdp));
  102. newSdp = transform.parse(newSdpStr);
  103. fidGroup = getVideoGroups(newSdp, 'FID')[0];
  104. const newFidGroupRtxSsrc = SDPUtil.parseGroupSsrcs(fidGroup)[1];
  105. expect(newFidGroupRtxSsrc).toEqual(fidGroupRtxSsrc);
  106. });
  107. it ('should NOT re-use the same rtx ssrc for a primary ssrc it\'s seen before if the cache has been cleared', function() {
  108. // Call modifyRtxSsrcs to generate an rtx ssrc
  109. // Clear the rtxModifier cache
  110. // Call modifyRtxSsrcs to generate an rtx ssrc again with the same primary ssrc
  111. // --> We should get a different rtx ssrc
  112. let newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.singleVideoSdp));
  113. let newSdp = transform.parse(newSdpStr);
  114. let fidGroup = getVideoGroups(newSdp, 'FID')[0];
  115. const fidGroupRtxSsrc = SDPUtil.parseGroupSsrcs(fidGroup)[1];
  116. this.rtxModifier.clearSsrcCache();
  117. // Now pass the original sdp through again
  118. newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.singleVideoSdp));
  119. newSdp = transform.parse(newSdpStr);
  120. fidGroup = getVideoGroups(newSdp, 'FID')[0];
  121. const newFidGroupRtxSsrc = SDPUtil.parseGroupSsrcs(fidGroup)[1];
  122. expect(newFidGroupRtxSsrc).not.toEqual(fidGroupRtxSsrc);
  123. });
  124. it ('should use the rtx ssrc from the cache when the cache has been manually set', function() {
  125. // Manually set an rtx ssrc mapping in the cache
  126. // Call modifyRtxSsrcs
  127. // -->The rtx ssrc used should be the one we set
  128. const forcedRtxSsrc = 123456;
  129. const ssrcCache = new Map();
  130. ssrcCache.set(this.primaryVideoSsrc, forcedRtxSsrc);
  131. this.rtxModifier.setSsrcCache(ssrcCache);
  132. const newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.singleVideoSdp));
  133. const newSdp = transform.parse(newSdpStr);
  134. const fidGroup = getVideoGroups(newSdp, 'FID')[0];
  135. const fidGroupRtxSsrc = SDPUtil.parseGroupSsrcs(fidGroup)[1];
  136. expect(fidGroupRtxSsrc).toEqual(forcedRtxSsrc);
  137. });
  138. });
  139. describe ('when given an sdp with multiple video ssrcs', function() {
  140. beforeEach(function() {
  141. this.multipleVideoSdp = SampleSdpStrings.simulcastSdp;
  142. this.primaryVideoSsrcs = getPrimaryVideoSsrcs(this.multipleVideoSdp);
  143. });
  144. it ('should add rtx ssrcs for all of them', function() {
  145. // Call rtxModifier.modifyRtxSsrcs with an sdp that contains multiple video
  146. // ssrcs. The returned sdp should have an rtx ssrc and an fid group for all of them.
  147. const newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.multipleVideoSdp));
  148. const newSdp = transform.parse(newSdpStr);
  149. const newPrimaryVideoSsrcs = getPrimaryVideoSsrcs(newSdp);
  150. expect(newPrimaryVideoSsrcs).toEqual(this.primaryVideoSsrcs);
  151. // Should now have rtx ssrcs as well
  152. expect(numVideoSsrcs(newSdp)).toEqual(this.primaryVideoSsrcs.length * 2);
  153. // Should now have FID groups
  154. const fidGroups = getVideoGroups(newSdp, 'FID');
  155. expect(fidGroups.length).toEqual(this.primaryVideoSsrcs.length);
  156. fidGroups.forEach(fidGroup => {
  157. const fidGroupPrimarySsrc = SDPUtil.parseGroupSsrcs(fidGroup)[0];
  158. expect(this.primaryVideoSsrcs.indexOf(fidGroupPrimarySsrc)).not.toEqual(-1);
  159. });
  160. });
  161. it ('should re-use the same rtx ssrcs for any primary ssrc it\'s seen before', function() {
  162. // Have rtxModifier generate an rtx ssrc via modifyRtxSsrcs. Then call it again
  163. // with the same primary ssrc in the sdp (but no rtx ssrc). It should use
  164. // the same rtx ssrc as before.
  165. let newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.multipleVideoSdp));
  166. let newSdp = transform.parse(newSdpStr);
  167. const rtxMapping = new Map();
  168. let fidGroups = getVideoGroups(newSdp, 'FID');
  169. // Save the first mapping that is made
  170. fidGroups.forEach(fidGroup => {
  171. const fidSsrcs = SDPUtil.parseGroupSsrcs(fidGroup);
  172. const fidGroupPrimarySsrc = fidSsrcs[0];
  173. const fidGroupRtxSsrc = fidSsrcs[1];
  174. rtxMapping.set(fidGroupPrimarySsrc, fidGroupRtxSsrc);
  175. });
  176. // Now pass the original sdp through again and make sure we get the same mapping
  177. newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.multipleVideoSdp));
  178. newSdp = transform.parse(newSdpStr);
  179. fidGroups = getVideoGroups(newSdp, 'FID');
  180. fidGroups.forEach(fidGroup => {
  181. const fidSsrcs = SDPUtil.parseGroupSsrcs(fidGroup);
  182. const fidGroupPrimarySsrc = fidSsrcs[0];
  183. const fidGroupRtxSsrc = fidSsrcs[1];
  184. expect(rtxMapping.has(fidGroupPrimarySsrc)).toBe(true);
  185. expect(rtxMapping.get(fidGroupPrimarySsrc)).toEqual(fidGroupRtxSsrc);
  186. });
  187. });
  188. it ('should NOT re-use the same rtx ssrcs for any primary ssrc it\'s seen before if the cache has been cleared', function() {
  189. // Call modifyRtxSsrcs to generate an rtx ssrc
  190. // Clear the rtxModifier cache
  191. // Call modifyRtxSsrcs to generate rtx ssrcs again with the same primary ssrcs
  192. // --> We should get different rtx ssrcs
  193. let newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.multipleVideoSdp));
  194. let newSdp = transform.parse(newSdpStr);
  195. const rtxMapping = new Map();
  196. let fidGroups = getVideoGroups(newSdp, 'FID');
  197. // Save the first mapping that is made
  198. fidGroups.forEach(fidGroup => {
  199. const fidSsrcs = SDPUtil.parseGroupSsrcs(fidGroup);
  200. const fidGroupPrimarySsrc = fidSsrcs[0];
  201. const fidGroupRtxSsrc = fidSsrcs[1];
  202. rtxMapping.set(fidGroupPrimarySsrc, fidGroupRtxSsrc);
  203. });
  204. this.rtxModifier.clearSsrcCache();
  205. // Now pass the original sdp through again and make sure we get the same mapping
  206. newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.multipleVideoSdp));
  207. newSdp = transform.parse(newSdpStr);
  208. fidGroups = getVideoGroups(newSdp, 'FID');
  209. fidGroups.forEach(fidGroup => {
  210. const fidSsrcs = SDPUtil.parseGroupSsrcs(fidGroup);
  211. const fidGroupPrimarySsrc = fidSsrcs[0];
  212. const fidGroupRtxSsrc = fidSsrcs[1];
  213. expect(rtxMapping.has(fidGroupPrimarySsrc)).toBe(true);
  214. expect(rtxMapping.get(fidGroupPrimarySsrc)).not.toEqual(fidGroupRtxSsrc);
  215. });
  216. });
  217. it ('should use the rtx ssrcs from the cache when the cache has been manually set', function() {
  218. // Manually set an rtx ssrc mapping in the cache
  219. // Call modifyRtxSsrcs
  220. // -->The rtx ssrc used should be the one we set
  221. const rtxMapping = new Map();
  222. this.primaryVideoSsrcs.forEach(ssrc => {
  223. rtxMapping.set(ssrc, SDPUtil.generateSsrc());
  224. });
  225. this.rtxModifier.setSsrcCache(rtxMapping);
  226. const newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(this.multipleVideoSdp));
  227. const newSdp = transform.parse(newSdpStr);
  228. const fidGroups = getVideoGroups(newSdp, 'FID');
  229. fidGroups.forEach(fidGroup => {
  230. const fidSsrcs = SDPUtil.parseGroupSsrcs(fidGroup);
  231. const fidGroupPrimarySsrc = fidSsrcs[0];
  232. const fidGroupRtxSsrc = fidSsrcs[1];
  233. expect(rtxMapping.has(fidGroupPrimarySsrc)).toBe(true);
  234. expect(rtxMapping.get(fidGroupPrimarySsrc)).toEqual(fidGroupRtxSsrc);
  235. });
  236. });
  237. });
  238. describe ('(corner cases)', function() {
  239. it ('should handle a recvonly video mline', function() {
  240. const sdp = SampleSdpStrings.plainVideoSdp;
  241. const videoMLine = sdp.media.find(m => m.type === 'video');
  242. videoMLine.direction = 'recvonly';
  243. const newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(sdp));
  244. expect(newSdpStr).toEqual(this.transform.write(sdp));
  245. });
  246. it ('should handle an inactive video mline', function() {
  247. const sdp = SampleSdpStrings.plainVideoSdp;
  248. const videoMLine = sdp.media.find(m => m.type === 'video');
  249. videoMLine.direction = 'inactive';
  250. const newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(sdp));
  251. expect(newSdpStr).toEqual(this.transform.write(sdp));
  252. });
  253. it ('should handle a video mline with no video ssrcs', function() {
  254. const sdp = SampleSdpStrings.plainVideoSdp;
  255. const videoMLine = sdp.media.find(m => m.type === 'video');
  256. videoMLine.ssrcs = [];
  257. const newSdpStr = this.rtxModifier.modifyRtxSsrcs(this.transform.write(sdp));
  258. expect(newSdpStr).toEqual(this.transform.write(sdp));
  259. });
  260. });
  261. });
  262. describe('stripRtx', function() {
  263. beforeEach(function() {
  264. });
  265. it ('should strip all rtx streams from an sdp with rtx', function() {
  266. const sdpStr = transform.write(SampleSdpStrings.rtxVideoSdp);
  267. const newSdpStr = this.rtxModifier.stripRtx(sdpStr);
  268. const newSdp = transform.parse(newSdpStr);
  269. const fidGroups = getVideoGroups(newSdp, 'FID');
  270. expect(fidGroups.length).toEqual(0);
  271. expect(numVideoSsrcs(newSdp)).toEqual(1);
  272. });
  273. it ('should do nothing to an sdp with no rtx', function() {
  274. const sdpStr = transform.write(SampleSdpStrings.plainVideoSdp);
  275. const newSdpStr = this.rtxModifier.stripRtx(sdpStr);
  276. expect(newSdpStr).toEqual(sdpStr);
  277. });
  278. });
  279. });
  280. /* eslint-enable max-len*/