iOS camera switching video capturer.

Introduces a new capture class derived from cricket::VideoCapturer that
provides the ability to switch cameras and updates AppRTCDemo to use it.
Some future work pending to clean up AppRTCDemo UI.

BUG=4070
R=magjed@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/48279005

Cr-Commit-Position: refs/heads/master@{#9137}
This commit is contained in:
Zeke Chin
2015-05-05 07:52:31 -07:00
parent 5cb9ce4c74
commit 57cc74e32c
17 changed files with 899 additions and 84 deletions

View File

@@ -27,7 +27,15 @@
#import "ARDAppClient+Internal.h"
#import <AVFoundation/AVFoundation.h>
#if defined(WEBRTC_IOS)
#import "RTCAVFoundationVideoSource.h"
#endif
#import "RTCICEServer.h"
#import "RTCMediaConstraints.h"
#import "RTCMediaStream.h"
#import "RTCPair.h"
#import "RTCVideoCapturer.h"
#import "RTCAVFoundationVideoSource.h"
#import "ARDAppEngineClient.h"
#import "ARDCEODTURNClient.h"
@@ -37,13 +45,8 @@
#import "ARDUtilities.h"
#import "ARDWebSocketChannel.h"
#import "RTCICECandidate+JSON.h"
#import "RTCICEServer.h"
#import "RTCMediaConstraints.h"
#import "RTCMediaStream.h"
#import "RTCPair.h"
#import "RTCSessionDescription+JSON.h"
#import "RTCVideoCapturer.h"
#import "RTCVideoTrack.h"
static NSString * const kARDDefaultSTUNServerUrl =
@"stun:stun.l.google.com:19302";
@@ -484,39 +487,33 @@ static NSInteger const kARDAppClientErrorInvalidRoom = -6;
- (RTCMediaStream *)createLocalMediaStream {
RTCMediaStream* localStream = [_factory mediaStreamWithLabel:@"ARDAMS"];
RTCVideoTrack* localVideoTrack = nil;
RTCVideoTrack* localVideoTrack = [self createLocalVideoTrack];
if (localVideoTrack) {
[localStream addVideoTrack:localVideoTrack];
[_delegate appClient:self didReceiveLocalVideoTrack:localVideoTrack];
}
[localStream addAudioTrack:[_factory audioTrackWithID:@"ARDAMSa0"]];
return localStream;
}
- (RTCVideoTrack *)createLocalVideoTrack {
RTCVideoTrack* localVideoTrack = nil;
// The iOS simulator doesn't provide any sort of camera capture
// support or emulation (http://goo.gl/rHAnC1) so don't bother
// trying to open a local stream.
// TODO(tkchin): local video capture for OSX. See
// https://code.google.com/p/webrtc/issues/detail?id=3417.
#if !TARGET_IPHONE_SIMULATOR && TARGET_OS_IPHONE
NSString *cameraID = nil;
for (AVCaptureDevice *captureDevice in
[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
if (captureDevice.position == AVCaptureDevicePositionFront) {
cameraID = [captureDevice localizedName];
break;
}
}
NSAssert(cameraID, @"Unable to get the front camera id");
RTCVideoCapturer *capturer =
[RTCVideoCapturer capturerWithDeviceName:cameraID];
RTCMediaConstraints *mediaConstraints = [self defaultMediaStreamConstraints];
RTCVideoSource *videoSource =
[_factory videoSourceWithCapturer:capturer
constraints:mediaConstraints];
RTCAVFoundationVideoSource *source =
[[RTCAVFoundationVideoSource alloc] initWithFactory:_factory
constraints:mediaConstraints];
localVideoTrack =
[_factory videoTrackWithID:@"ARDAMSv0" source:videoSource];
if (localVideoTrack) {
[localStream addVideoTrack:localVideoTrack];
}
[_delegate appClient:self didReceiveLocalVideoTrack:localVideoTrack];
[[RTCVideoTrack alloc] initWithFactory:_factory
source:source
trackId:@"ARDAMSv0"];
#endif
[localStream addAudioTrack:[_factory audioTrackWithID:@"ARDAMSa0"]];
return localStream;
return localVideoTrack;
}
#pragma mark - Collider methods

View File

@@ -32,6 +32,9 @@
@class ARDVideoCallView;
@protocol ARDVideoCallViewDelegate <NSObject>
// Called when the camera switch button is pressed.
- (void)videoCallViewDidSwitchCamera:(ARDVideoCallView *)view;
// Called when the hangup button is pressed.
- (void)videoCallViewDidHangup:(ARDVideoCallView *)view;

View File

@@ -30,19 +30,20 @@
#import <AVFoundation/AVFoundation.h>
#import "UIImage+ARDUtilities.h"
static CGFloat const kHangupButtonPadding = 16;
static CGFloat const kHangupButtonSize = 48;
static CGFloat const kLocalVideoViewWidth = 90;
static CGFloat const kLocalVideoViewHeight = 120;
static CGFloat const kButtonPadding = 16;
static CGFloat const kButtonSize = 48;
static CGFloat const kLocalVideoViewSize = 120;
static CGFloat const kLocalVideoViewPadding = 8;
@interface ARDVideoCallView () <RTCEAGLVideoViewDelegate>
@end
@implementation ARDVideoCallView {
UIButton *_cameraSwitchButton;
UIButton *_hangupButton;
CGSize _localVideoSize;
CGSize _remoteVideoSize;
BOOL _useRearCamera;
}
@synthesize statusLabel = _statusLabel;
@@ -56,17 +57,30 @@ static CGFloat const kLocalVideoViewPadding = 8;
_remoteVideoView.delegate = self;
[self addSubview:_remoteVideoView];
// TODO(tkchin): replace this with a view that renders layer from
// AVCaptureSession.
_localVideoView = [[RTCEAGLVideoView alloc] initWithFrame:CGRectZero];
_localVideoView.transform = CGAffineTransformMakeScale(-1, 1);
_localVideoView.delegate = self;
[self addSubview:_localVideoView];
// TODO(tkchin): don't display this if we can't actually do camera switch.
_cameraSwitchButton = [UIButton buttonWithType:UIButtonTypeCustom];
_cameraSwitchButton.backgroundColor = [UIColor whiteColor];
_cameraSwitchButton.layer.cornerRadius = kButtonSize / 2;
_cameraSwitchButton.layer.masksToBounds = YES;
UIImage *image = [UIImage imageNamed:@"ic_switch_video_black_24dp.png"];
[_cameraSwitchButton setImage:image forState:UIControlStateNormal];
[_cameraSwitchButton addTarget:self
action:@selector(onCameraSwitch:)
forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_cameraSwitchButton];
_hangupButton = [UIButton buttonWithType:UIButtonTypeCustom];
_hangupButton.backgroundColor = [UIColor redColor];
_hangupButton.layer.cornerRadius = kHangupButtonSize / 2;
_hangupButton.layer.cornerRadius = kButtonSize / 2;
_hangupButton.layer.masksToBounds = YES;
UIImage *image = [UIImage imageForName:@"ic_call_end_black_24dp.png"
color:[UIColor whiteColor]];
image = [UIImage imageForName:@"ic_call_end_black_24dp.png"
color:[UIColor whiteColor]];
[_hangupButton setImage:image forState:UIControlStateNormal];
[_hangupButton addTarget:self
action:@selector(onHangup:)
@@ -104,21 +118,36 @@ static CGFloat const kLocalVideoViewPadding = 8;
_remoteVideoView.frame = bounds;
}
CGRect localVideoFrame = CGRectZero;
localVideoFrame.origin.x =
CGRectGetMaxX(bounds) - kLocalVideoViewWidth - kLocalVideoViewPadding;
localVideoFrame.origin.y =
CGRectGetMaxY(bounds) - kLocalVideoViewHeight - kLocalVideoViewPadding;
localVideoFrame.size.width = kLocalVideoViewWidth;
localVideoFrame.size.height = kLocalVideoViewHeight;
_localVideoView.frame = localVideoFrame;
if (_localVideoSize.width && _localVideoSize.height > 0) {
// Aspect fit local video view into a square box.
CGRect localVideoFrame =
CGRectMake(0, 0, kLocalVideoViewSize, kLocalVideoViewSize);
localVideoFrame =
AVMakeRectWithAspectRatioInsideRect(_localVideoSize, localVideoFrame);
// Place the view in the bottom right.
localVideoFrame.origin.x = CGRectGetMaxX(bounds)
- localVideoFrame.size.width - kLocalVideoViewPadding;
localVideoFrame.origin.y = CGRectGetMaxY(bounds)
- localVideoFrame.size.height - kLocalVideoViewPadding;
_localVideoView.frame = localVideoFrame;
} else {
_localVideoView.frame = bounds;
}
// Place hangup button in the bottom left.
_hangupButton.frame =
CGRectMake(CGRectGetMinX(bounds) + kHangupButtonPadding,
CGRectGetMaxY(bounds) - kHangupButtonPadding -
kHangupButtonSize,
kHangupButtonSize,
kHangupButtonSize);
CGRectMake(CGRectGetMinX(bounds) + kButtonPadding,
CGRectGetMaxY(bounds) - kButtonPadding -
kButtonSize,
kButtonSize,
kButtonSize);
// Place button to the right of hangup button.
CGRect cameraSwitchFrame = _hangupButton.frame;
cameraSwitchFrame.origin.x =
CGRectGetMaxX(cameraSwitchFrame) + kButtonPadding;
_cameraSwitchButton.frame = cameraSwitchFrame;
[_statusLabel sizeToFit];
_statusLabel.center =
@@ -130,6 +159,7 @@ static CGFloat const kLocalVideoViewPadding = 8;
- (void)videoView:(RTCEAGLVideoView*)videoView didChangeVideoSize:(CGSize)size {
if (videoView == _localVideoView) {
_localVideoSize = size;
_localVideoView.hidden = CGSizeEqualToSize(CGSizeZero, _localVideoSize);
} else if (videoView == _remoteVideoView) {
_remoteVideoSize = size;
}
@@ -138,6 +168,10 @@ static CGFloat const kLocalVideoViewPadding = 8;
#pragma mark - Private
- (void)onCameraSwitch:(id)sender {
[_delegate videoCallViewDidSwitchCamera:self];
}
- (void)onHangup:(id)sender {
[_delegate videoCallViewDidHangup:self];
}

View File

@@ -27,11 +27,15 @@
#import "ARDVideoCallViewController.h"
#import "RTCAVFoundationVideoSource.h"
#import "ARDAppClient.h"
#import "ARDVideoCallView.h"
@interface ARDVideoCallViewController () <ARDAppClientDelegate,
ARDVideoCallViewDelegate>
@property(nonatomic, strong) RTCVideoTrack *localVideoTrack;
@property(nonatomic, strong) RTCVideoTrack *remoteVideoTrack;
@property(nonatomic, readonly) ARDVideoCallView *videoCallView;
@end
@@ -90,19 +94,13 @@
- (void)appClient:(ARDAppClient *)client
didReceiveLocalVideoTrack:(RTCVideoTrack *)localVideoTrack {
if (!_localVideoTrack) {
_localVideoTrack = localVideoTrack;
[_localVideoTrack addRenderer:_videoCallView.localVideoView];
}
self.localVideoTrack = localVideoTrack;
}
- (void)appClient:(ARDAppClient *)client
didReceiveRemoteVideoTrack:(RTCVideoTrack *)remoteVideoTrack {
if (!_remoteVideoTrack) {
_remoteVideoTrack = remoteVideoTrack;
[_remoteVideoTrack addRenderer:_videoCallView.remoteVideoView];
_videoCallView.statusLabel.hidden = YES;
}
self.remoteVideoTrack = remoteVideoTrack;
_videoCallView.statusLabel.hidden = YES;
}
- (void)appClient:(ARDAppClient *)client
@@ -119,24 +117,54 @@
[self hangup];
}
- (void)videoCallViewDidSwitchCamera:(ARDVideoCallView *)view {
// TODO(tkchin): Rate limit this so you can't tap continously on it.
// Probably through an animation.
[self switchCamera];
}
#pragma mark - Private
- (void)setLocalVideoTrack:(RTCVideoTrack *)localVideoTrack {
if (_localVideoTrack == localVideoTrack) {
return;
}
[_localVideoTrack removeRenderer:_videoCallView.localVideoView];
_localVideoTrack = nil;
[_videoCallView.localVideoView renderFrame:nil];
_localVideoTrack = localVideoTrack;
[_localVideoTrack addRenderer:_videoCallView.localVideoView];
}
- (void)setRemoteVideoTrack:(RTCVideoTrack *)remoteVideoTrack {
if (_remoteVideoTrack == remoteVideoTrack) {
return;
}
[_remoteVideoTrack removeRenderer:_videoCallView.localVideoView];
_remoteVideoTrack = nil;
[_videoCallView.remoteVideoView renderFrame:nil];
_remoteVideoTrack = remoteVideoTrack;
[_remoteVideoTrack addRenderer:_videoCallView.remoteVideoView];
}
- (void)hangup {
if (_remoteVideoTrack) {
[_remoteVideoTrack removeRenderer:_videoCallView.remoteVideoView];
_remoteVideoTrack = nil;
[_videoCallView.remoteVideoView renderFrame:nil];
}
if (_localVideoTrack) {
[_localVideoTrack removeRenderer:_videoCallView.localVideoView];
_localVideoTrack = nil;
[_videoCallView.localVideoView renderFrame:nil];
}
self.remoteVideoTrack = nil;
self.localVideoTrack = nil;
[_client disconnect];
[self.presentingViewController dismissViewControllerAnimated:YES
completion:nil];
}
- (void)switchCamera {
RTCVideoSource* source = self.localVideoTrack.source;
if ([source isKindOfClass:[RTCAVFoundationVideoSource class]]) {
RTCAVFoundationVideoSource* avSource = (RTCAVFoundationVideoSource*)source;
avSource.useBackCamera = !avSource.useBackCamera;
_videoCallView.localVideoView.transform = avSource.useBackCamera ?
CGAffineTransformIdentity : CGAffineTransformMakeScale(-1, 1);
}
}
- (NSString *)statusTextForState:(RTCICEConnectionState)state {
switch (state) {
case RTCICEConnectionNew:

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B