123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- import { MockPeerConnection, MockRTC } from '../RTC/MockClasses';
- import { nextTick } from '../util/TestUtils';
- import JingleSessionPC from '../xmpp/JingleSessionPC';
- import { MockChatRoom, MockStropheConnection } from '../xmpp/MockClasses';
-
- import { MockConference, MockLocalTrack, MockParticipant } from './MockClasses';
- import { FixedSizeArray, QualityController } from './QualityController';
-
- describe('Codec Selection', () => {
- let qualityController;
- let conference;
- let connection;
- let jingleSession;
- let options;
- let participant1, participant2, participant3;
- let rtc;
- const SID = 'sid12345';
- let tpc;
-
- beforeEach(() => {
- rtc = new MockRTC();
- conference = new MockConference(rtc);
- connection = new MockStropheConnection();
- jingleSession = new JingleSessionPC(
- SID,
- 'peer1',
- 'peer2',
- connection,
- { },
- { },
- false,
- false);
-
- jingleSession.initialize(
- /* ChatRoom */ new MockChatRoom(),
- /* RTC */ rtc,
- /* Signaling layer */ conference._signalingLayer,
- /* options */ { });
- conference.jvbJingleSession = jingleSession;
- });
-
- describe('when codec preference list is used in config.js', () => {
- beforeEach(() => {
- options = {
- jvb: {
- preferenceOrder: [ 'VP9', 'VP8', 'H264' ],
- screenshareCodec: 'VP9'
- },
- p2p: {}
- };
-
- qualityController = new QualityController(conference, options);
- jasmine.clock().install();
- spyOn(jingleSession, 'setVideoCodecs');
- });
-
- afterEach(() => {
- jasmine.clock().uninstall();
- });
-
- it('and remote endpoints use the new codec selection logic', async () => {
- // Add a second user joining the call.
- participant1 = new MockParticipant('remote-1');
- conference.addParticipant(participant1, [ 'vp9', 'vp8' ]);
-
- await nextTick(1000);
-
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(1);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp9', 'vp8' ], 'vp9');
-
- // Add a third user joining the call with a subset of codecs.
- participant2 = new MockParticipant('remote-2');
- conference.addParticipant(participant2, [ 'vp8' ]);
-
- // Make p2 leave the call.
- conference.removeParticipant(participant2);
-
- await nextTick(1000);
-
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(2);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp9', 'vp8' ], 'vp9');
- });
-
- it('and remote endpoints use the old codec selection logic (RN)', async () => {
- // Add a second user joining the call.
- participant1 = new MockParticipant('remote-1');
- conference.addParticipant(participant1, null, 'vp8');
-
- await nextTick(1000);
-
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(1);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8' ], 'vp9');
-
- // Add a third user (newer) to the call.
- participant2 = new MockParticipant('remote-2');
- conference.addParticipant(participant2, [ 'vp9', 'vp8' ]);
-
- // Make p1 leave the call
- conference.removeParticipant(participant1);
-
- await nextTick(1000);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(2);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp9', 'vp8' ], 'vp9');
- });
- });
-
- describe('when deprecated configs are used in config.js', () => {
- beforeEach(() => {
- options = {
- jvb: {
- preferredCodec: 'VP9',
- disabledCodec: 'H264'
- },
- p2p: {}
- };
-
- qualityController = new QualityController(conference, options);
- spyOn(jingleSession, 'setVideoCodecs');
- jasmine.clock().install();
- });
-
- afterEach(() => {
- jasmine.clock().uninstall();
- });
-
- it('and remote endpoints use the new codec selection logic', async () => {
- // Add a second user joining the call.
- participant1 = new MockParticipant('remote-1');
- conference.addParticipant(participant1, [ 'vp9', 'vp8', 'h264' ]);
-
- await nextTick(1000);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(1);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp9', 'vp8' ], undefined);
-
- // Add a third user joining the call with a subset of codecs.
- participant2 = new MockParticipant('remote-2');
- conference.addParticipant(participant2, [ 'vp8' ]);
-
- // Make p2 leave the call
- conference.removeParticipant(participant2);
-
- await nextTick(1000);
-
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(2);
- });
-
- it('and remote endpoint prefers a codec that is locally disabled', async () => {
- // Add a second user joining the call the prefers H.264 and VP8.
- participant1 = new MockParticipant('remote-1');
- conference.addParticipant(participant1, [ 'h264', 'vp8' ]);
-
- await nextTick(1200);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8' ], undefined);
- });
-
- it('and remote endpoints use the old codec selection logic (RN)', async () => {
- // Add a second user joining the call.
- participant1 = new MockParticipant('remote-1');
- conference.addParticipant(participant1, null, 'vp8');
-
- await nextTick(1000);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(1);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8' ], undefined);
-
- // Add a third user (newer) to the call.
- participant2 = new MockParticipant('remote-2');
- conference.addParticipant(participant2, [ 'vp9', 'vp8', 'h264' ]);
-
- // Make p1 leave the call
- conference.removeParticipant(participant1);
-
- jasmine.clock().tick(1000);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(2);
- });
- });
-
- describe('when codec switching is triggered based on outbound-rtp stats', () => {
- beforeEach(() => {
- options = {
- jvb: {
- preferenceOrder: [ 'AV1', 'VP9', 'VP8' ]
- },
- p2p: {}
- };
- jasmine.clock().install();
- qualityController = new QualityController(conference, options);
- spyOn(jingleSession, 'setVideoCodecs');
- });
-
- afterEach(() => {
- jasmine.clock().uninstall();
- });
-
- it('and encode resolution is limited by cpu for camera tracks', async () => {
- const localTrack = new MockLocalTrack('1', 720, 'camera');
-
- participant1 = new MockParticipant('remote-1');
- conference.addParticipant(participant1, [ 'av1', 'vp9', 'vp8' ]);
-
- await nextTick(1000);
-
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'av1', 'vp9', 'vp8' ], undefined);
-
- participant2 = new MockParticipant('remote-2');
- conference.addParticipant(participant2, [ 'av1', 'vp9', 'vp8' ]);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(1);
-
- qualityController.codecController.changeCodecPreferenceOrder(localTrack, 'av1');
-
- await nextTick(121000);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp9', 'av1', 'vp8' ], undefined);
-
- participant3 = new MockParticipant('remote-3');
- conference.addParticipant(participant3, [ 'av1', 'vp9', 'vp8' ]);
-
- // Expect the local endpoint to continue sending VP9.
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp9', 'av1', 'vp8' ], undefined);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(3);
- });
-
- it('and does not change codec if the current codec is already the lowest complexity codec', async () => {
- const localTrack = new MockLocalTrack('1', 720, 'camera');
-
- qualityController.codecController.codecPreferenceOrder.jvb = [ 'vp8', 'vp9', 'av1' ];
-
- participant1 = new MockParticipant('remote-1');
- conference.addParticipant(participant1, [ 'av1', 'vp9', 'vp8' ]);
-
- await nextTick(1000);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8', 'vp9', 'av1' ], undefined);
-
- participant2 = new MockParticipant('remote-2');
- conference.addParticipant(participant2, [ 'av1', 'vp9', 'vp8' ]);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8', 'vp9', 'av1' ], undefined);
-
- qualityController.codecController.changeCodecPreferenceOrder(localTrack, 'vp8');
-
- await nextTick(121000);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8', 'vp9', 'av1' ], undefined);
-
- participant3 = new MockParticipant('remote-3');
- conference.addParticipant(participant3, [ 'av1', 'vp9', 'vp8' ]);
-
- // Expect the local endpoint to continue sending VP9.
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8', 'vp9', 'av1' ], undefined);
- });
- });
-
- describe('when codec switching should be triggered based on outbound-rtp stats', () => {
- beforeEach(() => {
- options = {
- enableAdaptiveMode: true,
- jvb: {
- preferenceOrder: [ 'AV1', 'VP9', 'VP8' ]
- },
- p2p: {}
- };
- jasmine.clock().install();
- tpc = new MockPeerConnection();
- qualityController = new QualityController(conference, options);
- spyOn(jingleSession, 'setVideoCodecs');
- });
-
- afterEach(() => {
- jasmine.clock().uninstall();
- });
-
- it('and encode resolution is limited by cpu for camera tracks', async () => {
- const localTrack = new MockLocalTrack('1', 720, 'camera');
-
- participant1 = new MockParticipant('remote-1');
- conference.addParticipant(participant1, [ 'av1', 'vp9', 'vp8' ]);
-
- await nextTick(1000);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(1);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'av1', 'vp9', 'vp8' ], undefined);
-
- participant2 = new MockParticipant('remote-2');
- conference.addParticipant(participant2, [ 'av1', 'vp9', 'vp8' ]);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(1);
-
- const sourceStats = {
- avgEncodeTime: 12,
- codec: 'AV1',
- encodeResolution: 360,
- qualityLimitationReason: 'cpu',
- localTrack,
- timestamp: 1,
- tpc
- };
-
- qualityController._encodeTimeStats = new Map();
- const data = new FixedSizeArray(10);
-
- data.add(sourceStats);
- qualityController._encodeTimeStats.set(localTrack.rtcId, data);
-
- qualityController._performQualityOptimizations(sourceStats);
- await nextTick(60000);
-
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp9', 'av1', 'vp8' ], undefined);
-
- participant3 = new MockParticipant('remote-3');
- conference.addParticipant(participant3, [ 'av1', 'vp9', 'vp8' ]);
-
- await nextTick(1000);
-
- // Expect the local endpoint to continue sending VP9.
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp9', 'av1', 'vp8' ], undefined);
-
- // If the cpu limitation continues to exist, client should switch to vp8.
- const updatedStats = {
- avgEncodeTime: 12,
- codec: 'VP9',
- encodeResolution: 360,
- qualityLimitationReason: 'cpu',
- localTrack,
- timestamp: 1,
- tpc
- };
-
- data.add(updatedStats);
- qualityController._performQualityOptimizations(updatedStats);
- await nextTick(60000);
-
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'vp8', 'vp9', 'av1' ], undefined);
- });
- });
-
- describe('When codec switching should not be triggered based on outbound-rtp stats', () => {
- beforeEach(() => {
- options = {
- enableAdaptiveMode: false,
- jvb: {
- preferenceOrder: [ 'AV1', 'VP9', 'VP8' ]
- },
- p2p: {}
- };
- jasmine.clock().install();
- tpc = new MockPeerConnection();
- qualityController = new QualityController(conference, options);
- spyOn(jingleSession, 'setVideoCodecs');
- });
-
- afterEach(() => {
- jasmine.clock().uninstall();
- });
-
- it('and the client encounters cpu limitation with high complexity codec', async () => {
- const localTrack = new MockLocalTrack('1', 720, 'camera');
- const sourceStats = {
- avgEncodeTime: 12,
- codec: 'AV1',
- encodeResolution: 360,
- qualityLimitationReason: 'cpu',
- localTrack,
- timestamp: 1,
- tpc
- };
-
- qualityController._performQualityOptimizations(sourceStats);
- await nextTick(60000);
-
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(0);
- });
- });
-
- describe('When multiple joins and leaves happen in a quick burst', () => {
- beforeEach(() => {
- options = {
- jvb: {
- preferenceOrder: [ 'AV1', 'VP9', 'VP8' ],
- screenshareCodec: 'VP9'
- },
- p2p: {}
- };
- jasmine.clock().install();
- qualityController = new QualityController(conference, options);
- spyOn(jingleSession, 'setVideoCodecs');
- });
-
- afterEach(() => {
- jasmine.clock().uninstall();
- });
-
- it('should call setVideoCodecs only once within the same tick', async () => {
- participant1 = new MockParticipant('remote-1');
- conference.addParticipant(participant1, [ 'vp9', 'vp8' ]);
-
- // Add a third user joining the call with a subset of codecs.
- participant2 = new MockParticipant('remote-2');
- conference.addParticipant(participant2, [ 'vp8' ]);
-
- // Make p1 and p2 leave the call.
- conference.removeParticipant(participant2);
- conference.removeParticipant(participant1);
-
- await nextTick(1000);
-
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledTimes(1);
- expect(jingleSession.setVideoCodecs).toHaveBeenCalledWith([ 'av1', 'vp9', 'vp8' ], 'vp9');
- });
- });
- });
|