/* * Copyright 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #import "base/RTCVideoDecoderFactory.h" #import "base/RTCVideoEncoderFactory.h" #import "api/peerconnection/RTCAudioSource.h" #import "api/peerconnection/RTCConfiguration.h" #import "api/peerconnection/RTCDataChannel.h" #import "api/peerconnection/RTCDataChannelConfiguration.h" #import "api/peerconnection/RTCMediaConstraints.h" #import "api/peerconnection/RTCMediaStreamTrack.h" #import "api/peerconnection/RTCPeerConnection.h" #import "api/peerconnection/RTCPeerConnectionFactory.h" #import "api/peerconnection/RTCRtpCapabilities.h" #import "api/peerconnection/RTCRtpCodecCapability.h" #import "api/peerconnection/RTCRtpReceiver.h" #import "api/peerconnection/RTCRtpSender.h" #import "api/peerconnection/RTCRtpTransceiver.h" #import "api/peerconnection/RTCSessionDescription.h" #import "api/peerconnection/RTCVideoSource.h" #import "rtc_base/system/unused.h" #import @interface MockVideoEncoderDecoderFactory : NSObject - (instancetype)initWithSupportedCodecs: (nonnull NSArray *)supportedCodecs; @end @implementation MockVideoEncoderDecoderFactory { NSArray *_supportedCodecs; } - (instancetype)initWithSupportedCodecs: (nonnull NSArray *)supportedCodecs { if (self = [super init]) { _supportedCodecs = supportedCodecs; } return self; } - (nullable id)createEncoder: (nonnull RTC_OBJC_TYPE(RTCVideoCodecInfo) *)info { return nil; } - (nullable id)createDecoder: (nonnull RTC_OBJC_TYPE(RTCVideoCodecInfo) *)info { return nil; } - (nonnull NSArray *)supportedCodecs { return _supportedCodecs; } @end @interface RTCPeerConnectionFactoryTests : XCTestCase @end @implementation RTCPeerConnectionFactoryTests - (void)testPeerConnectionLifetime { @autoreleasepool { RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{} optionalConstraints:nil]; RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; RTC_OBJC_TYPE(RTCPeerConnection) * peerConnection; @autoreleasepool { factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; peerConnection = [factory peerConnectionWithConfiguration:config constraints:constraints delegate:nil]; [peerConnection close]; factory = nil; } peerConnection = nil; } XCTAssertTrue(true, @"Expect test does not crash"); } - (void)testMediaStreamLifetime { @autoreleasepool { RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; RTC_OBJC_TYPE(RTCMediaStream) * mediaStream; @autoreleasepool { factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; mediaStream = [factory mediaStreamWithStreamId:@"mediaStream"]; factory = nil; } mediaStream = nil; RTC_UNUSED(mediaStream); } XCTAssertTrue(true, "Expect test does not crash"); } - (void)testDataChannelLifetime { @autoreleasepool { RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{} optionalConstraints:nil]; RTC_OBJC_TYPE(RTCDataChannelConfiguration) *dataChannelConfig = [[RTC_OBJC_TYPE(RTCDataChannelConfiguration) alloc] init]; RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; RTC_OBJC_TYPE(RTCPeerConnection) * peerConnection; RTC_OBJC_TYPE(RTCDataChannel) * dataChannel; @autoreleasepool { factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; peerConnection = [factory peerConnectionWithConfiguration:config constraints:constraints delegate:nil]; dataChannel = [peerConnection dataChannelForLabel:@"test_channel" configuration:dataChannelConfig]; XCTAssertNotNil(dataChannel); [peerConnection close]; peerConnection = nil; factory = nil; } dataChannel = nil; } XCTAssertTrue(true, "Expect test does not crash"); } - (void)testRTCRtpTransceiverLifetime { @autoreleasepool { RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; config.sdpSemantics = RTCSdpSemanticsUnifiedPlan; RTC_OBJC_TYPE(RTCMediaConstraints) *contraints = [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{} optionalConstraints:nil]; RTC_OBJC_TYPE(RTCRtpTransceiverInit) *init = [[RTC_OBJC_TYPE(RTCRtpTransceiverInit) alloc] init]; RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; RTC_OBJC_TYPE(RTCPeerConnection) * peerConnection; RTC_OBJC_TYPE(RTCRtpTransceiver) * tranceiver; @autoreleasepool { factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; peerConnection = [factory peerConnectionWithConfiguration:config constraints:contraints delegate:nil]; tranceiver = [peerConnection addTransceiverOfType:RTCRtpMediaTypeAudio init:init]; XCTAssertNotNil(tranceiver); [peerConnection close]; peerConnection = nil; factory = nil; } tranceiver = nil; } XCTAssertTrue(true, "Expect test does not crash"); } - (void)testRTCRtpSenderLifetime { @autoreleasepool { RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; config.sdpSemantics = RTCSdpSemanticsPlanB; RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{} optionalConstraints:nil]; RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; RTC_OBJC_TYPE(RTCPeerConnection) * peerConnection; RTC_OBJC_TYPE(RTCRtpSender) * sender; @autoreleasepool { factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; peerConnection = [factory peerConnectionWithConfiguration:config constraints:constraints delegate:nil]; sender = [peerConnection senderWithKind:kRTCMediaStreamTrackKindVideo streamId:@"stream"]; XCTAssertNotNil(sender); [peerConnection close]; peerConnection = nil; factory = nil; } sender = nil; } XCTAssertTrue(true, "Expect test does not crash"); } - (void)testRTCRtpReceiverLifetime { @autoreleasepool { RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; config.sdpSemantics = RTCSdpSemanticsPlanB; RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{} optionalConstraints:nil]; RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; RTC_OBJC_TYPE(RTCPeerConnection) * pc1; RTC_OBJC_TYPE(RTCPeerConnection) * pc2; NSArray *receivers1; NSArray *receivers2; @autoreleasepool { factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; pc1 = [factory peerConnectionWithConfiguration:config constraints:constraints delegate:nil]; [pc1 senderWithKind:kRTCMediaStreamTrackKindAudio streamId:@"stream"]; pc2 = [factory peerConnectionWithConfiguration:config constraints:constraints delegate:nil]; [pc2 senderWithKind:kRTCMediaStreamTrackKindAudio streamId:@"stream"]; NSTimeInterval negotiationTimeout = 15; XCTAssertTrue([self negotiatePeerConnection:pc1 withPeerConnection:pc2 negotiationTimeout:negotiationTimeout]); XCTAssertEqual(pc1.signalingState, RTCSignalingStateStable); XCTAssertEqual(pc2.signalingState, RTCSignalingStateStable); receivers1 = pc1.receivers; receivers2 = pc2.receivers; XCTAssertTrue(receivers1.count > 0); XCTAssertTrue(receivers2.count > 0); [pc1 close]; [pc2 close]; pc1 = nil; pc2 = nil; factory = nil; } receivers1 = nil; receivers2 = nil; } XCTAssertTrue(true, "Expect test does not crash"); } - (void)testAudioSourceLifetime { @autoreleasepool { RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; RTC_OBJC_TYPE(RTCAudioSource) * audioSource; @autoreleasepool { factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; audioSource = [factory audioSourceWithConstraints:nil]; XCTAssertNotNil(audioSource); factory = nil; } audioSource = nil; } XCTAssertTrue(true, "Expect test does not crash"); } - (void)testVideoSourceLifetime { @autoreleasepool { RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; RTC_OBJC_TYPE(RTCVideoSource) * videoSource; @autoreleasepool { factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; videoSource = [factory videoSource]; XCTAssertNotNil(videoSource); factory = nil; } videoSource = nil; } XCTAssertTrue(true, "Expect test does not crash"); } - (void)testAudioTrackLifetime { @autoreleasepool { RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; RTC_OBJC_TYPE(RTCAudioTrack) * audioTrack; @autoreleasepool { factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; audioTrack = [factory audioTrackWithTrackId:@"audioTrack"]; XCTAssertNotNil(audioTrack); factory = nil; } audioTrack = nil; } XCTAssertTrue(true, "Expect test does not crash"); } - (void)testVideoTrackLifetime { @autoreleasepool { RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; RTC_OBJC_TYPE(RTCVideoTrack) * videoTrack; @autoreleasepool { factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; videoTrack = [factory videoTrackWithSource:[factory videoSource] trackId:@"videoTrack"]; XCTAssertNotNil(videoTrack); factory = nil; } videoTrack = nil; } XCTAssertTrue(true, "Expect test does not crash"); } - (void)testRollback { @autoreleasepool { RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; config.sdpSemantics = RTCSdpSemanticsUnifiedPlan; RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{ kRTCMediaConstraintsOfferToReceiveAudio : kRTCMediaConstraintsValueTrue } optionalConstraints:nil]; __block RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; __block RTC_OBJC_TYPE(RTCPeerConnection) * pc1; RTC_OBJC_TYPE(RTCSessionDescription) *rollback = [[RTC_OBJC_TYPE(RTCSessionDescription) alloc] initWithType:RTCSdpTypeRollback sdp:@""]; @autoreleasepool { factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; pc1 = [factory peerConnectionWithConfiguration:config constraints:constraints delegate:nil]; dispatch_semaphore_t negotiatedSem = dispatch_semaphore_create(0); [pc1 offerForConstraints:constraints completionHandler:^(RTC_OBJC_TYPE(RTCSessionDescription) * offer, NSError * error) { XCTAssertNil(error); XCTAssertNotNil(offer); __weak RTC_OBJC_TYPE(RTCPeerConnection) *weakPC1 = pc1; [pc1 setLocalDescription:offer completionHandler:^(NSError *error) { XCTAssertNil(error); [weakPC1 setLocalDescription:rollback completionHandler:^(NSError *error) { XCTAssertNil(error); }]; }]; NSTimeInterval negotiationTimeout = 15; dispatch_semaphore_wait( negotiatedSem, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(negotiationTimeout * NSEC_PER_SEC))); XCTAssertEqual(pc1.signalingState, RTCSignalingStateStable); [pc1 close]; pc1 = nil; factory = nil; }]; } XCTAssertTrue(true, "Expect test does not crash"); } } - (void)testSenderCapabilities { @autoreleasepool { RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; MockVideoEncoderDecoderFactory *encoder; MockVideoEncoderDecoderFactory *decoder; NSArray *supportedCodecs = @[ [[RTC_OBJC_TYPE(RTCVideoCodecInfo) alloc] initWithName:@"VP8"], [[RTC_OBJC_TYPE(RTCVideoCodecInfo) alloc] initWithName:@"H264"] ]; encoder = [[MockVideoEncoderDecoderFactory alloc] initWithSupportedCodecs:supportedCodecs]; decoder = [[MockVideoEncoderDecoderFactory alloc] initWithSupportedCodecs:supportedCodecs]; factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] initWithEncoderFactory:encoder decoderFactory:decoder]; RTC_OBJC_TYPE(RTCRtpCapabilities) *capabilities = [factory rtpSenderCapabilitiesForKind:kRTCMediaStreamTrackKindVideo]; NSMutableArray *codecNames = [NSMutableArray new]; for (RTC_OBJC_TYPE(RTCRtpCodecCapability) * codec in capabilities.codecs) { [codecNames addObject:codec.name]; } XCTAssertTrue([codecNames containsObject:@"VP8"]); XCTAssertTrue([codecNames containsObject:@"H264"]); factory = nil; } } - (void)testReceiverCapabilities { @autoreleasepool { RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; MockVideoEncoderDecoderFactory *encoder; MockVideoEncoderDecoderFactory *decoder; NSArray *supportedCodecs = @[ [[RTC_OBJC_TYPE(RTCVideoCodecInfo) alloc] initWithName:@"VP8"], [[RTC_OBJC_TYPE(RTCVideoCodecInfo) alloc] initWithName:@"H264"] ]; encoder = [[MockVideoEncoderDecoderFactory alloc] initWithSupportedCodecs:supportedCodecs]; decoder = [[MockVideoEncoderDecoderFactory alloc] initWithSupportedCodecs:supportedCodecs]; factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] initWithEncoderFactory:encoder decoderFactory:decoder]; RTC_OBJC_TYPE(RTCRtpCapabilities) *capabilities = [factory rtpReceiverCapabilitiesForKind:kRTCMediaStreamTrackKindVideo]; NSMutableArray *codecNames = [NSMutableArray new]; for (RTC_OBJC_TYPE(RTCRtpCodecCapability) * codec in capabilities.codecs) { [codecNames addObject:codec.name]; } XCTAssertTrue([codecNames containsObject:@"VP8"]); XCTAssertTrue([codecNames containsObject:@"H264"]); factory = nil; } } - (void)testSetCodecPreferences { @autoreleasepool { RTC_OBJC_TYPE(RTCConfiguration) *config = [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:nil optionalConstraints:nil]; RTC_OBJC_TYPE(RTCRtpTransceiverInit) *init = [[RTC_OBJC_TYPE(RTCRtpTransceiverInit) alloc] init]; NSArray *supportedCodecs = @[ [[RTC_OBJC_TYPE(RTCVideoCodecInfo) alloc] initWithName:@"VP8"], [[RTC_OBJC_TYPE(RTCVideoCodecInfo) alloc] initWithName:@"H264"] ]; MockVideoEncoderDecoderFactory *encoder = [[MockVideoEncoderDecoderFactory alloc] initWithSupportedCodecs:supportedCodecs]; MockVideoEncoderDecoderFactory *decoder = [[MockVideoEncoderDecoderFactory alloc] initWithSupportedCodecs:supportedCodecs]; RTC_OBJC_TYPE(RTCPeerConnectionFactory) * factory; RTC_OBJC_TYPE(RTCPeerConnection) * peerConnection; RTC_OBJC_TYPE(RTCRtpTransceiver) * tranceiver; factory = [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] initWithEncoderFactory:encoder decoderFactory:decoder]; peerConnection = [factory peerConnectionWithConfiguration:config constraints:constraints delegate:nil]; tranceiver = [peerConnection addTransceiverOfType:RTCRtpMediaTypeVideo init:init]; XCTAssertNotNil(tranceiver); RTC_OBJC_TYPE(RTCRtpCapabilities) *capabilities = [factory rtpReceiverCapabilitiesForKind:kRTCMediaStreamTrackKindVideo]; RTC_OBJC_TYPE(RTCRtpCodecCapability) * targetCodec; for (RTC_OBJC_TYPE(RTCRtpCodecCapability) * codec in capabilities.codecs) { if ([codec.name isEqual:@"VP8"]) { targetCodec = codec; break; } } XCTAssertNotNil(targetCodec); [tranceiver setCodecPreferences:@[ targetCodec ]]; @autoreleasepool { dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); __block BOOL completed = NO; [peerConnection offerForConstraints:constraints completionHandler:^(RTC_OBJC_TYPE(RTCSessionDescription) *_Nullable sdp, NSError *_Nullable error) { XCTAssertNil(error); XCTAssertNotNil(sdp); NSArray *rtpMaps = [self rtpMapsFromSDP:sdp.sdp]; XCTAssertEqual(1, rtpMaps.count); XCTAssertNotNil(targetCodec.preferredPayloadType); XCTAssertNotNil(targetCodec.clockRate); NSString *expected = [NSString stringWithFormat:@"a=rtpmap:%i VP8/%i", targetCodec.preferredPayloadType.intValue, targetCodec.clockRate.intValue]; XCTAssertTrue([expected isEqualToString:rtpMaps[0]]); completed = YES; dispatch_semaphore_signal(semaphore); }]; [peerConnection close]; peerConnection = nil; factory = nil; tranceiver = nil; dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 15.0 * NSEC_PER_SEC)); XCTAssertTrue(completed); } } } - (bool)negotiatePeerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)pc1 withPeerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)pc2 negotiationTimeout:(NSTimeInterval)timeout { __weak RTC_OBJC_TYPE(RTCPeerConnection) *weakPC1 = pc1; __weak RTC_OBJC_TYPE(RTCPeerConnection) *weakPC2 = pc2; RTC_OBJC_TYPE(RTCMediaConstraints) *sdpConstraints = [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:@{ kRTCMediaConstraintsOfferToReceiveAudio : kRTCMediaConstraintsValueTrue } optionalConstraints:nil]; dispatch_semaphore_t negotiatedSem = dispatch_semaphore_create(0); [weakPC1 offerForConstraints:sdpConstraints completionHandler:^(RTC_OBJC_TYPE(RTCSessionDescription) * offer, NSError * error) { XCTAssertNil(error); XCTAssertNotNil(offer); [weakPC1 setLocalDescription:offer completionHandler:^(NSError *error) { XCTAssertNil(error); [weakPC2 setRemoteDescription:offer completionHandler:^(NSError *error) { XCTAssertNil(error); [weakPC2 answerForConstraints:sdpConstraints completionHandler:^( RTC_OBJC_TYPE(RTCSessionDescription) * answer, NSError * error) { XCTAssertNil(error); XCTAssertNotNil(answer); [weakPC2 setLocalDescription:answer completionHandler:^(NSError *error) { XCTAssertNil(error); [weakPC1 setRemoteDescription:answer completionHandler:^(NSError *error) { XCTAssertNil(error); dispatch_semaphore_signal(negotiatedSem); }]; }]; }]; }]; }]; }]; return 0 == dispatch_semaphore_wait(negotiatedSem, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC))); } - (NSArray *)rtpMapsFromSDP:(NSString *)sdp { NSMutableArray *rtpMaps = [NSMutableArray new]; NSArray *sdpLines = [sdp componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; for (NSString *line in sdpLines) { if ([line hasPrefix:@"a=rtpmap"]) { [rtpMaps addObject:line]; } } return rtpMaps; } @end