This remove the use of VideoTrackRenderers within VideoTrack and instead all its sinks are passed to VideoSource. That means that the source will handle all sinks and can (if the source implement it) handle different SinkWants for each sink. The VideoBroadcaster is updated to produce black frames instead of as is today the deprecated VideoTrackRenderers. BUG=webrtc:5426 R=nisse@webrtc.org, pthatcher@webrtc.org Review URL: https://codereview.webrtc.org/1779063003 . Cr-Commit-Position: refs/heads/master@{#12028}
351 lines
16 KiB
Plaintext
351 lines
16 KiB
Plaintext
/*
|
|
* libjingle
|
|
* Copyright 2013 Google Inc.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
* 3. The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#import <Foundation/Foundation.h>
|
|
|
|
#import "RTCICEServer.h"
|
|
#import "RTCMediaConstraints.h"
|
|
#import "RTCMediaStream.h"
|
|
#import "RTCPair.h"
|
|
#import "RTCPeerConnection.h"
|
|
#import "RTCPeerConnectionFactory.h"
|
|
#import "RTCPeerConnectionSyncObserver.h"
|
|
#import "RTCSessionDescription.h"
|
|
#import "RTCSessionDescriptionSyncObserver.h"
|
|
#import "RTCVideoRenderer.h"
|
|
#import "RTCVideoTrack.h"
|
|
|
|
#include "webrtc/base/gunit.h"
|
|
#include "webrtc/base/ssladapter.h"
|
|
|
|
#if !defined(__has_feature) || !__has_feature(objc_arc)
|
|
#error "This file requires ARC support."
|
|
#endif
|
|
|
|
const NSTimeInterval kRTCPeerConnectionTestTimeout = 20;
|
|
|
|
@interface RTCFakeRenderer : NSObject <RTCVideoRenderer>
|
|
@end
|
|
|
|
@implementation RTCFakeRenderer
|
|
|
|
- (void)setSize:(CGSize)size {}
|
|
- (void)renderFrame:(RTCI420Frame*)frame {}
|
|
|
|
@end
|
|
|
|
@interface RTCPeerConnectionTest : NSObject
|
|
|
|
// Returns whether the two sessions are of the same type.
|
|
+ (BOOL)isSession:(RTCSessionDescription*)session1
|
|
ofSameTypeAsSession:(RTCSessionDescription*)session2;
|
|
|
|
// Create and add tracks to pc, with the given source, label, and IDs
|
|
- (RTCMediaStream*)addTracksToPeerConnection:(RTCPeerConnection*)pc
|
|
withFactory:(RTCPeerConnectionFactory*)factory
|
|
videoSource:(RTCVideoSource*)videoSource
|
|
streamLabel:(NSString*)streamLabel
|
|
videoTrackID:(NSString*)videoTrackID
|
|
audioTrackID:(NSString*)audioTrackID;
|
|
|
|
- (void)testCompleteSessionWithFactory:(RTCPeerConnectionFactory*)factory;
|
|
|
|
@end
|
|
|
|
@implementation RTCPeerConnectionTest
|
|
|
|
+ (BOOL)isSession:(RTCSessionDescription*)session1
|
|
ofSameTypeAsSession:(RTCSessionDescription*)session2 {
|
|
return [session1.type isEqual:session2.type];
|
|
}
|
|
|
|
- (RTCMediaStream*)addTracksToPeerConnection:(RTCPeerConnection*)pc
|
|
withFactory:(RTCPeerConnectionFactory*)factory
|
|
videoSource:(RTCVideoSource*)videoSource
|
|
streamLabel:(NSString*)streamLabel
|
|
videoTrackID:(NSString*)videoTrackID
|
|
audioTrackID:(NSString*)audioTrackID {
|
|
RTCMediaStream* localMediaStream = [factory mediaStreamWithLabel:streamLabel];
|
|
// TODO(zeke): Fix this test to create a fake video capturer so that a track
|
|
// can be created.
|
|
if (videoSource) {
|
|
RTCVideoTrack* videoTrack =
|
|
[factory videoTrackWithID:videoTrackID source:videoSource];
|
|
RTCFakeRenderer* videoRenderer = [[RTCFakeRenderer alloc] init];
|
|
[videoTrack addRenderer:videoRenderer];
|
|
[localMediaStream addVideoTrack:videoTrack];
|
|
// Test that removal/re-add works.
|
|
[localMediaStream removeVideoTrack:videoTrack];
|
|
[localMediaStream addVideoTrack:videoTrack];
|
|
}
|
|
RTCAudioTrack* audioTrack = [factory audioTrackWithID:audioTrackID];
|
|
[localMediaStream addAudioTrack:audioTrack];
|
|
[pc addStream:localMediaStream];
|
|
return localMediaStream;
|
|
}
|
|
|
|
- (void)testCompleteSessionWithFactory:(RTCPeerConnectionFactory*)factory {
|
|
NSArray* mandatory = @[
|
|
[[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:@"true"],
|
|
[[RTCPair alloc] initWithKey:@"internalSctpDataChannels" value:@"true"],
|
|
];
|
|
RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc] init];
|
|
RTCMediaConstraints* pcConstraints =
|
|
[[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatory
|
|
optionalConstraints:nil];
|
|
|
|
RTCPeerConnectionSyncObserver* offeringExpectations =
|
|
[[RTCPeerConnectionSyncObserver alloc] init];
|
|
RTCPeerConnection* pcOffer =
|
|
[factory peerConnectionWithICEServers:nil
|
|
constraints:pcConstraints
|
|
delegate:offeringExpectations];
|
|
|
|
RTCPeerConnectionSyncObserver* answeringExpectations =
|
|
[[RTCPeerConnectionSyncObserver alloc] init];
|
|
|
|
RTCPeerConnection* pcAnswer =
|
|
[factory peerConnectionWithICEServers:nil
|
|
constraints:pcConstraints
|
|
delegate:answeringExpectations];
|
|
// TODO(hughv): Create video capturer
|
|
RTCVideoCapturer* capturer = nil;
|
|
RTCVideoSource* videoSource =
|
|
[factory videoSourceWithCapturer:capturer constraints:constraints];
|
|
|
|
// Here and below, "oLMS" refers to offerer's local media stream, and "aLMS"
|
|
// refers to the answerer's local media stream, with suffixes of "a0" and "v0"
|
|
// for audio and video tracks, resp. These mirror chrome historical naming.
|
|
RTCMediaStream* oLMSUnused = [self addTracksToPeerConnection:pcOffer
|
|
withFactory:factory
|
|
videoSource:videoSource
|
|
streamLabel:@"oLMS"
|
|
videoTrackID:@"oLMSv0"
|
|
audioTrackID:@"oLMSa0"];
|
|
|
|
RTCDataChannel* offerDC =
|
|
[pcOffer createDataChannelWithLabel:@"offerDC"
|
|
config:[[RTCDataChannelInit alloc] init]];
|
|
EXPECT_TRUE([offerDC.label isEqual:@"offerDC"]);
|
|
offerDC.delegate = offeringExpectations;
|
|
offeringExpectations.dataChannel = offerDC;
|
|
|
|
RTCSessionDescriptionSyncObserver* sdpObserver =
|
|
[[RTCSessionDescriptionSyncObserver alloc] init];
|
|
[pcOffer createOfferWithDelegate:sdpObserver constraints:constraints];
|
|
[sdpObserver wait];
|
|
EXPECT_TRUE(sdpObserver.success);
|
|
RTCSessionDescription* offerSDP = sdpObserver.sessionDescription;
|
|
EXPECT_EQ([@"offer" compare:offerSDP.type options:NSCaseInsensitiveSearch],
|
|
NSOrderedSame);
|
|
EXPECT_GT([offerSDP.description length], 0);
|
|
|
|
sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init];
|
|
[answeringExpectations expectSignalingChange:RTCSignalingHaveRemoteOffer];
|
|
[answeringExpectations expectAddStream:@"oLMS"];
|
|
[pcAnswer setRemoteDescriptionWithDelegate:sdpObserver
|
|
sessionDescription:offerSDP];
|
|
[sdpObserver wait];
|
|
|
|
RTCMediaStream* aLMSUnused = [self addTracksToPeerConnection:pcAnswer
|
|
withFactory:factory
|
|
videoSource:videoSource
|
|
streamLabel:@"aLMS"
|
|
videoTrackID:@"aLMSv0"
|
|
audioTrackID:@"aLMSa0"];
|
|
|
|
sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init];
|
|
[pcAnswer createAnswerWithDelegate:sdpObserver constraints:constraints];
|
|
[sdpObserver wait];
|
|
EXPECT_TRUE(sdpObserver.success);
|
|
RTCSessionDescription* answerSDP = sdpObserver.sessionDescription;
|
|
EXPECT_EQ([@"answer" compare:answerSDP.type options:NSCaseInsensitiveSearch],
|
|
NSOrderedSame);
|
|
EXPECT_GT([answerSDP.description length], 0);
|
|
|
|
[offeringExpectations expectICECandidates:2];
|
|
// It's possible to only have 1 ICE candidate for the answerer, since we use
|
|
// BUNDLE and rtcp-mux by default, and don't provide any ICE servers in this
|
|
// test.
|
|
[answeringExpectations expectICECandidates:1];
|
|
|
|
sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init];
|
|
[answeringExpectations expectSignalingChange:RTCSignalingStable];
|
|
[pcAnswer setLocalDescriptionWithDelegate:sdpObserver
|
|
sessionDescription:answerSDP];
|
|
[sdpObserver wait];
|
|
EXPECT_TRUE(sdpObserver.sessionDescription == NULL);
|
|
|
|
sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init];
|
|
[offeringExpectations expectSignalingChange:RTCSignalingHaveLocalOffer];
|
|
[pcOffer setLocalDescriptionWithDelegate:sdpObserver
|
|
sessionDescription:offerSDP];
|
|
[sdpObserver wait];
|
|
EXPECT_TRUE(sdpObserver.sessionDescription == NULL);
|
|
|
|
[offeringExpectations expectICEConnectionChange:RTCICEConnectionChecking];
|
|
[offeringExpectations expectICEConnectionChange:RTCICEConnectionConnected];
|
|
// TODO(fischman): figure out why this is flaky and re-introduce (and remove
|
|
// special-casing from the observer!).
|
|
// [offeringExpectations expectICEConnectionChange:RTCICEConnectionCompleted];
|
|
[answeringExpectations expectICEConnectionChange:RTCICEConnectionChecking];
|
|
[answeringExpectations expectICEConnectionChange:RTCICEConnectionConnected];
|
|
|
|
[offeringExpectations expectStateChange:kRTCDataChannelStateOpen];
|
|
[answeringExpectations expectDataChannel:@"offerDC"];
|
|
[answeringExpectations expectStateChange:kRTCDataChannelStateOpen];
|
|
|
|
[offeringExpectations expectICEGatheringChange:RTCICEGatheringComplete];
|
|
[answeringExpectations expectICEGatheringChange:RTCICEGatheringComplete];
|
|
|
|
sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init];
|
|
[offeringExpectations expectSignalingChange:RTCSignalingStable];
|
|
[offeringExpectations expectAddStream:@"aLMS"];
|
|
[pcOffer setRemoteDescriptionWithDelegate:sdpObserver
|
|
sessionDescription:answerSDP];
|
|
[sdpObserver wait];
|
|
EXPECT_TRUE(sdpObserver.sessionDescription == NULL);
|
|
|
|
EXPECT_TRUE([offerSDP.type isEqual:pcOffer.localDescription.type]);
|
|
EXPECT_TRUE([answerSDP.type isEqual:pcOffer.remoteDescription.type]);
|
|
EXPECT_TRUE([offerSDP.type isEqual:pcAnswer.remoteDescription.type]);
|
|
EXPECT_TRUE([answerSDP.type isEqual:pcAnswer.localDescription.type]);
|
|
|
|
for (RTCICECandidate* candidate in offeringExpectations
|
|
.releaseReceivedICECandidates) {
|
|
[pcAnswer addICECandidate:candidate];
|
|
}
|
|
for (RTCICECandidate* candidate in answeringExpectations
|
|
.releaseReceivedICECandidates) {
|
|
[pcOffer addICECandidate:candidate];
|
|
}
|
|
|
|
EXPECT_TRUE(
|
|
[offeringExpectations waitForAllExpectationsToBeSatisfiedWithTimeout:
|
|
kRTCPeerConnectionTestTimeout]);
|
|
EXPECT_TRUE(
|
|
[answeringExpectations waitForAllExpectationsToBeSatisfiedWithTimeout:
|
|
kRTCPeerConnectionTestTimeout]);
|
|
|
|
EXPECT_EQ(pcOffer.signalingState, RTCSignalingStable);
|
|
EXPECT_EQ(pcAnswer.signalingState, RTCSignalingStable);
|
|
|
|
// Test send and receive UTF-8 text
|
|
NSString* text = @"你好";
|
|
NSData* textData = [text dataUsingEncoding:NSUTF8StringEncoding];
|
|
RTCDataBuffer* buffer =
|
|
[[RTCDataBuffer alloc] initWithData:textData isBinary:NO];
|
|
[answeringExpectations expectMessage:[textData copy] isBinary:NO];
|
|
EXPECT_TRUE([offeringExpectations.dataChannel sendData:buffer]);
|
|
EXPECT_TRUE(
|
|
[answeringExpectations waitForAllExpectationsToBeSatisfiedWithTimeout:
|
|
kRTCPeerConnectionTestTimeout]);
|
|
|
|
// Test send and receive binary data
|
|
const size_t byteLength = 5;
|
|
char bytes[byteLength] = {1, 2, 3, 4, 5};
|
|
NSData* byteData = [NSData dataWithBytes:bytes length:byteLength];
|
|
buffer = [[RTCDataBuffer alloc] initWithData:byteData isBinary:YES];
|
|
[answeringExpectations expectMessage:[byteData copy] isBinary:YES];
|
|
EXPECT_TRUE([offeringExpectations.dataChannel sendData:buffer]);
|
|
EXPECT_TRUE(
|
|
[answeringExpectations waitForAllExpectationsToBeSatisfiedWithTimeout:
|
|
kRTCPeerConnectionTestTimeout]);
|
|
|
|
[offeringExpectations expectStateChange:kRTCDataChannelStateClosing];
|
|
[answeringExpectations expectStateChange:kRTCDataChannelStateClosing];
|
|
[offeringExpectations expectStateChange:kRTCDataChannelStateClosed];
|
|
[answeringExpectations expectStateChange:kRTCDataChannelStateClosed];
|
|
|
|
[answeringExpectations.dataChannel close];
|
|
[offeringExpectations.dataChannel close];
|
|
|
|
EXPECT_TRUE(
|
|
[offeringExpectations waitForAllExpectationsToBeSatisfiedWithTimeout:
|
|
kRTCPeerConnectionTestTimeout]);
|
|
EXPECT_TRUE(
|
|
[answeringExpectations waitForAllExpectationsToBeSatisfiedWithTimeout:
|
|
kRTCPeerConnectionTestTimeout]);
|
|
// Don't need to listen to further state changes.
|
|
// TODO(tkchin): figure out why Closed->Closing without this.
|
|
offeringExpectations.dataChannel.delegate = nil;
|
|
answeringExpectations.dataChannel.delegate = nil;
|
|
|
|
// Let the audio feedback run for 2s to allow human testing and to ensure
|
|
// things stabilize. TODO(fischman): replace seconds with # of video frames,
|
|
// when we have video flowing.
|
|
[[NSRunLoop currentRunLoop]
|
|
runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
|
|
|
|
[offeringExpectations expectICEConnectionChange:RTCICEConnectionClosed];
|
|
[answeringExpectations expectICEConnectionChange:RTCICEConnectionClosed];
|
|
[offeringExpectations expectSignalingChange:RTCSignalingClosed];
|
|
[answeringExpectations expectSignalingChange:RTCSignalingClosed];
|
|
|
|
[pcOffer close];
|
|
[pcAnswer close];
|
|
|
|
EXPECT_TRUE(
|
|
[offeringExpectations waitForAllExpectationsToBeSatisfiedWithTimeout:
|
|
kRTCPeerConnectionTestTimeout]);
|
|
EXPECT_TRUE(
|
|
[answeringExpectations waitForAllExpectationsToBeSatisfiedWithTimeout:
|
|
kRTCPeerConnectionTestTimeout]);
|
|
|
|
capturer = nil;
|
|
videoSource = nil;
|
|
pcOffer = nil;
|
|
pcAnswer = nil;
|
|
// TODO(fischman): be stricter about shutdown checks; ensure thread
|
|
// counts return to where they were before the test kicked off, and
|
|
// that all objects have in fact shut down.
|
|
}
|
|
|
|
@end
|
|
|
|
// TODO(fischman): move {Initialize,Cleanup}SSL into alloc/dealloc of
|
|
// RTCPeerConnectionTest and avoid the appearance of RTCPeerConnectionTest being
|
|
// a TestBase since it's not.
|
|
TEST(RTCPeerConnectionTest, SessionTest) {
|
|
@autoreleasepool {
|
|
rtc::InitializeSSL();
|
|
// Since |factory| will own the signaling & worker threads, it's important
|
|
// that it outlive the created PeerConnections since they self-delete on the
|
|
// signaling thread, and if |factory| is freed first then a last refcount on
|
|
// the factory will expire during this teardown, causing the signaling
|
|
// thread to try to Join() with itself. This is a hack to ensure that the
|
|
// factory outlives RTCPeerConnection:dealloc.
|
|
// See https://code.google.com/p/webrtc/issues/detail?id=3100.
|
|
RTCPeerConnectionFactory* factory = [[RTCPeerConnectionFactory alloc] init];
|
|
@autoreleasepool {
|
|
RTCPeerConnectionTest* pcTest = [[RTCPeerConnectionTest alloc] init];
|
|
[pcTest testCompleteSessionWithFactory:factory];
|
|
}
|
|
rtc::CleanupSSL();
|
|
}
|
|
}
|