Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

RtxModifier.spec.js 15KB

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