Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

CodecSelection.spec.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. import { MockPeerConnection, MockRTC } from '../RTC/MockClasses';
  2. import { nextTick } from '../util/TestUtils';
  3. import JingleSessionPC from '../xmpp/JingleSessionPC';
  4. import { MockChatRoom, MockStropheConnection } from '../xmpp/MockClasses';
  5. import { MockConference, MockLocalTrack, MockParticipant } from './MockClasses';
  6. import { FixedSizeArray, QualityController } from './QualityController';
  7. describe('Codec Selection', () => {
  8. let qualityController;
  9. let conference;
  10. let connection;
  11. let jingleSession;
  12. let options;
  13. let participant1, participant2, participant3;
  14. let rtc;
  15. const SID = 'sid12345';
  16. let tpc;
  17. beforeEach(() => {
  18. rtc = new MockRTC();
  19. conference = new MockConference(rtc);
  20. connection = new MockStropheConnection();
  21. jingleSession = new JingleSessionPC(
  22. SID,
  23. 'peer1',
  24. 'peer2',
  25. connection,
  26. { },
  27. { },
  28. false,
  29. false);
  30. jingleSession.initialize(
  31. /* ChatRoom */ new MockChatRoom(),
  32. /* RTC */ rtc,
  33. /* Signaling layer */ conference._signalingLayer,
  34. /* options */ { });
  35. conference.jvbJingleSession = jingleSession;
  36. });
  37. describe('when codec preference list is used in config.js', () => {
  38. beforeEach(() => {
  39. options = {
  40. jvb: {
  41. preferenceOrder: [ 'VP9', 'VP8', 'H264' ],
  42. screenshareCodec: 'VP9'
  43. },
  44. p2p: {}
  45. };
  46. qualityController = new QualityController(conference, options);
  47. spyOn(jingleSession, 'setVideoCodecs');
  48. });
  49. it('and remote endpoints use the new codec selection logic', () => {
  50. // Add a second user joining the call.
  51. participant1 = new MockParticipant('remote-1');
  52. conference.addParticipant(participant1, [ 'vp9', 'vp8' ]);
  53. expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(1);
  54. // Add a third user joining the call with a subset of codecs.
  55. participant2 = new MockParticipant('remote-2');
  56. conference.addParticipant(participant2, [ 'vp8' ]);
  57. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8' ], 'vp9');
  58. // Make p2 leave the call
  59. conference.removeParticipant(participant2);
  60. expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(3);
  61. });
  62. it('and remote endpoints use the old codec selection logic (RN)', () => {
  63. // Add a second user joining the call.
  64. participant1 = new MockParticipant('remote-1');
  65. conference.addParticipant(participant1, null, 'vp8');
  66. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8' ], 'vp9');
  67. // Add a third user (newer) to the call.
  68. participant2 = new MockParticipant('remote-2');
  69. conference.addParticipant(participant2, [ 'vp9', 'vp8' ]);
  70. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8' ], 'vp9');
  71. // Make p1 leave the call
  72. conference.removeParticipant(participant1);
  73. expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(3);
  74. });
  75. });
  76. describe('when deprecated configs are used in config.js', () => {
  77. beforeEach(() => {
  78. options = {
  79. jvb: {
  80. preferredCodec: 'VP9',
  81. disabledCodec: 'H264'
  82. },
  83. p2p: {}
  84. };
  85. qualityController = new QualityController(conference, options);
  86. spyOn(jingleSession, 'setVideoCodecs');
  87. });
  88. it('and remote endpoints use the new codec selection logic', () => {
  89. // Add a second user joining the call.
  90. participant1 = new MockParticipant('remote-1');
  91. conference.addParticipant(participant1, [ 'vp9', 'vp8', 'h264' ]);
  92. expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(1);
  93. // Add a third user joining the call with a subset of codecs.
  94. participant2 = new MockParticipant('remote-2');
  95. conference.addParticipant(participant2, [ 'vp8' ]);
  96. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8' ], undefined);
  97. // Make p2 leave the call
  98. conference.removeParticipant(participant2);
  99. expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(3);
  100. });
  101. it('and remote endpoint prefers a codec that is locally disabled', () => {
  102. // Add a second user joining the call the prefers H.264 and VP8.
  103. participant1 = new MockParticipant('remote-1');
  104. conference.addParticipant(participant1, [ 'h264', 'vp8' ]);
  105. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8' ], undefined);
  106. });
  107. it('and remote endpoints use the old codec selection logic (RN)', () => {
  108. // Add a second user joining the call.
  109. participant1 = new MockParticipant('remote-1');
  110. conference.addParticipant(participant1, null, 'vp8');
  111. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8' ], undefined);
  112. // Add a third user (newer) to the call.
  113. participant2 = new MockParticipant('remote-2');
  114. conference.addParticipant(participant2, [ 'vp9', 'vp8', 'h264' ]);
  115. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8' ], undefined);
  116. // Make p1 leave the call
  117. conference.removeParticipant(participant1);
  118. expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(3);
  119. });
  120. });
  121. describe('when codec switching is triggered based on outbound-rtp stats', () => {
  122. beforeEach(() => {
  123. options = {
  124. jvb: {
  125. preferenceOrder: [ 'AV1', 'VP9', 'VP8' ]
  126. },
  127. p2p: {}
  128. };
  129. jasmine.clock().install();
  130. qualityController = new QualityController(conference, options);
  131. spyOn(jingleSession, 'setVideoCodecs');
  132. });
  133. afterEach(() => {
  134. jasmine.clock().uninstall();
  135. });
  136. it('and encode resolution is limited by cpu for camera tracks', async () => {
  137. const localTrack = new MockLocalTrack('1', 720, 'camera');
  138. participant1 = new MockParticipant('remote-1');
  139. conference.addParticipant(participant1, [ 'av1', 'vp9', 'vp8' ]);
  140. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'av1', 'vp9', 'vp8' ], undefined);
  141. participant2 = new MockParticipant('remote-2');
  142. conference.addParticipant(participant2, [ 'av1', 'vp9', 'vp8' ]);
  143. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'av1', 'vp9', 'vp8' ], undefined);
  144. qualityController.codecController.changeCodecPreferenceOrder(localTrack, 'av1');
  145. await nextTick(121000);
  146. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp9', 'av1', 'vp8' ], undefined);
  147. participant3 = new MockParticipant('remote-3');
  148. conference.addParticipant(participant3, [ 'av1', 'vp9', 'vp8' ]);
  149. // Expect the local endpoint to continue sending VP9.
  150. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp9', 'av1', 'vp8' ], undefined);
  151. });
  152. it('and does not change codec if the current codec is already the lowest complexity codec', async () => {
  153. const localTrack = new MockLocalTrack('1', 720, 'camera');
  154. qualityController.codecController.codecPreferenceOrder.jvb = [ 'vp8', 'vp9', 'av1' ];
  155. participant1 = new MockParticipant('remote-1');
  156. conference.addParticipant(participant1, [ 'av1', 'vp9', 'vp8' ]);
  157. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8', 'vp9', 'av1' ], undefined);
  158. participant2 = new MockParticipant('remote-2');
  159. conference.addParticipant(participant2, [ 'av1', 'vp9', 'vp8' ]);
  160. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8', 'vp9', 'av1' ], undefined);
  161. qualityController.codecController.changeCodecPreferenceOrder(localTrack, 'vp8');
  162. await nextTick(121000);
  163. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8', 'vp9', 'av1' ], undefined);
  164. participant3 = new MockParticipant('remote-3');
  165. conference.addParticipant(participant3, [ 'av1', 'vp9', 'vp8' ]);
  166. // Expect the local endpoint to continue sending VP9.
  167. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8', 'vp9', 'av1' ], undefined);
  168. });
  169. });
  170. describe('when codec switching should be triggered based on outbound-rtp stats', () => {
  171. beforeEach(() => {
  172. options = {
  173. enableAdaptiveMode: true,
  174. jvb: {
  175. preferenceOrder: [ 'AV1', 'VP9', 'VP8' ]
  176. },
  177. p2p: {}
  178. };
  179. jasmine.clock().install();
  180. tpc = new MockPeerConnection();
  181. qualityController = new QualityController(conference, options);
  182. spyOn(jingleSession, 'setVideoCodecs');
  183. });
  184. afterEach(() => {
  185. jasmine.clock().uninstall();
  186. });
  187. it('and encode resolution is limited by cpu for camera tracks', async () => {
  188. const localTrack = new MockLocalTrack('1', 720, 'camera');
  189. participant1 = new MockParticipant('remote-1');
  190. conference.addParticipant(participant1, [ 'av1', 'vp9', 'vp8' ]);
  191. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'av1', 'vp9', 'vp8' ], undefined);
  192. participant2 = new MockParticipant('remote-2');
  193. conference.addParticipant(participant2, [ 'av1', 'vp9', 'vp8' ]);
  194. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'av1', 'vp9', 'vp8' ], undefined);
  195. const sourceStats = {
  196. avgEncodeTime: 12,
  197. codec: 'AV1',
  198. encodeResolution: 360,
  199. qualityLimitationReason: 'cpu',
  200. localTrack,
  201. timestamp: 1,
  202. tpc
  203. };
  204. qualityController._encodeTimeStats = new Map();
  205. const data = new FixedSizeArray(10);
  206. data.add(sourceStats);
  207. qualityController._encodeTimeStats.set(localTrack.rtcId, data);
  208. qualityController._performQualityOptimizations(sourceStats);
  209. await nextTick(60000);
  210. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp9', 'av1', 'vp8' ], undefined);
  211. participant3 = new MockParticipant('remote-3');
  212. conference.addParticipant(participant3, [ 'av1', 'vp9', 'vp8' ]);
  213. // Expect the local endpoint to continue sending VP9.
  214. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp9', 'av1', 'vp8' ], undefined);
  215. // If the cpu limitation continues to exist, client should switch to vp8.
  216. const updatedStats = {
  217. avgEncodeTime: 12,
  218. codec: 'VP9',
  219. encodeResolution: 360,
  220. qualityLimitationReason: 'cpu',
  221. localTrack,
  222. timestamp: 1,
  223. tpc
  224. };
  225. data.add(updatedStats);
  226. qualityController._performQualityOptimizations(updatedStats);
  227. await nextTick(60000);
  228. expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8', 'vp9', 'av1' ], undefined);
  229. });
  230. });
  231. describe('When codec switching should not be triggered based on outbound-rtp stats', () => {
  232. beforeEach(() => {
  233. options = {
  234. enableAdaptiveMode: false,
  235. jvb: {
  236. preferenceOrder: [ 'AV1', 'VP9', 'VP8' ]
  237. },
  238. p2p: {}
  239. };
  240. jasmine.clock().install();
  241. tpc = new MockPeerConnection();
  242. qualityController = new QualityController(conference, options);
  243. spyOn(jingleSession, 'setVideoCodecs');
  244. });
  245. afterEach(() => {
  246. jasmine.clock().uninstall();
  247. });
  248. it('and the client encounters cpu limitation with high complexity codec', async () => {
  249. const localTrack = new MockLocalTrack('1', 720, 'camera');
  250. const sourceStats = {
  251. avgEncodeTime: 12,
  252. codec: 'AV1',
  253. encodeResolution: 360,
  254. qualityLimitationReason: 'cpu',
  255. localTrack,
  256. timestamp: 1,
  257. tpc
  258. };
  259. qualityController._performQualityOptimizations(sourceStats);
  260. await nextTick(60000);
  261. expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(0);
  262. });
  263. });
  264. });