blob: e36c83bad9485517b69f9acea5ab5fde74ba3b0e [file] [log] [blame]
/*
* Copyright (c) 2013 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.
*/
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#import <UIKit/UIKit.h>
#import "webrtc/modules/video_capture/ios/device_info_ios_objc.h"
#import "webrtc/modules/video_capture/ios/rtc_video_capture_ios_objc.h"
#include "webrtc/system_wrappers/include/trace.h"
using namespace webrtc;
using namespace webrtc::videocapturemodule;
@interface RTCVideoCaptureIosObjC (hidden)
- (int)changeCaptureInputWithName:(NSString*)captureDeviceName;
@end
@implementation RTCVideoCaptureIosObjC {
webrtc::videocapturemodule::VideoCaptureIos* _owner;
webrtc::VideoCaptureCapability _capability;
AVCaptureSession* _captureSession;
int _captureId;
BOOL _orientationHasChanged;
AVCaptureConnection* _connection;
BOOL _captureChanging; // Guarded by _captureChangingCondition.
NSCondition* _captureChangingCondition;
}
@synthesize frameRotation = _framRotation;
- (id)initWithOwner:(VideoCaptureIos*)owner captureId:(int)captureId {
if (self == [super init]) {
_owner = owner;
_captureId = captureId;
_captureSession = [[AVCaptureSession alloc] init];
#if defined(__IPHONE_7_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_7_0
NSString* version = [[UIDevice currentDevice] systemVersion];
if ([version integerValue] >= 7) {
_captureSession.usesApplicationAudioSession = NO;
}
#endif
_captureChanging = NO;
_captureChangingCondition = [[NSCondition alloc] init];
if (!_captureSession || !_captureChangingCondition) {
return nil;
}
// create and configure a new output (using callbacks)
AVCaptureVideoDataOutput* captureOutput =
[[AVCaptureVideoDataOutput alloc] init];
NSString* key = (NSString*)kCVPixelBufferPixelFormatTypeKey;
NSNumber* val = [NSNumber
numberWithUnsignedInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange];
NSDictionary* videoSettings =
[NSDictionary dictionaryWithObject:val forKey:key];
captureOutput.videoSettings = videoSettings;
// add new output
if ([_captureSession canAddOutput:captureOutput]) {
[_captureSession addOutput:captureOutput];
} else {
WEBRTC_TRACE(kTraceError,
kTraceVideoCapture,
_captureId,
"%s:%s:%d Could not add output to AVCaptureSession ",
__FILE__,
__FUNCTION__,
__LINE__);
}
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
NSNotificationCenter* notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self
selector:@selector(onVideoError:)
name:AVCaptureSessionRuntimeErrorNotification
object:_captureSession];
[notify addObserver:self
selector:@selector(deviceOrientationDidChange:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
return self;
}
- (void)directOutputToSelf {
[[self currentOutput]
setSampleBufferDelegate:self
queue:dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
}
- (void)directOutputToNil {
[[self currentOutput] setSampleBufferDelegate:nil queue:NULL];
}
- (void)deviceOrientationDidChange:(NSNotification*)notification {
_orientationHasChanged = YES;
[self setRelativeVideoOrientation];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (BOOL)setCaptureDeviceByUniqueId:(NSString*)uniqueId {
[self waitForCaptureChangeToFinish];
// check to see if the camera is already set
if (_captureSession) {
NSArray* currentInputs = [NSArray arrayWithArray:[_captureSession inputs]];
if ([currentInputs count] > 0) {
AVCaptureDeviceInput* currentInput = [currentInputs objectAtIndex:0];
if ([uniqueId isEqualToString:[currentInput.device localizedName]]) {
return YES;
}
}
}
return [self changeCaptureInputByUniqueId:uniqueId];
}
- (BOOL)startCaptureWithCapability:(const VideoCaptureCapability&)capability {
[self waitForCaptureChangeToFinish];
if (!_captureSession) {
return NO;
}
// check limits of the resolution
if (capability.maxFPS < 0 || capability.maxFPS > 60) {
return NO;
}
if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1920x1080]) {
if (capability.width > 1920 || capability.height > 1080) {
return NO;
}
} else if ([_captureSession
canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
if (capability.width > 1280 || capability.height > 720) {
return NO;
}
} else if ([_captureSession
canSetSessionPreset:AVCaptureSessionPreset640x480]) {
if (capability.width > 640 || capability.height > 480) {
return NO;
}
} else if ([_captureSession
canSetSessionPreset:AVCaptureSessionPreset352x288]) {
if (capability.width > 352 || capability.height > 288) {
return NO;
}
} else if (capability.width < 0 || capability.height < 0) {
return NO;
}
_capability = capability;
AVCaptureVideoDataOutput* currentOutput = [self currentOutput];
if (!currentOutput)
return NO;
[self directOutputToSelf];
_orientationHasChanged = NO;
_captureChanging = YES;
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^(void) { [self startCaptureInBackgroundWithOutput:currentOutput]; });
return YES;
}
- (AVCaptureVideoDataOutput*)currentOutput {
return [[_captureSession outputs] firstObject];
}
- (void)startCaptureInBackgroundWithOutput:
(AVCaptureVideoDataOutput*)currentOutput {
NSString* captureQuality =
[NSString stringWithString:AVCaptureSessionPresetLow];
if (_capability.width >= 1920 || _capability.height >= 1080) {
captureQuality =
[NSString stringWithString:AVCaptureSessionPreset1920x1080];
} else if (_capability.width >= 1280 || _capability.height >= 720) {
captureQuality = [NSString stringWithString:AVCaptureSessionPreset1280x720];
} else if (_capability.width >= 640 || _capability.height >= 480) {
captureQuality = [NSString stringWithString:AVCaptureSessionPreset640x480];
} else if (_capability.width >= 352 || _capability.height >= 288) {
captureQuality = [NSString stringWithString:AVCaptureSessionPreset352x288];
}
// begin configuration for the AVCaptureSession
[_captureSession beginConfiguration];
// picture resolution
[_captureSession setSessionPreset:captureQuality];
// take care of capture framerate now
NSArray* sessionInputs = _captureSession.inputs;
AVCaptureDeviceInput* deviceInput = [sessionInputs count] > 0 ?
sessionInputs[0] : nil;
AVCaptureDevice* inputDevice = deviceInput.device;
if (inputDevice) {
AVCaptureDeviceFormat* activeFormat = inputDevice.activeFormat;
NSArray* supportedRanges = activeFormat.videoSupportedFrameRateRanges;
AVFrameRateRange* targetRange = [supportedRanges count] > 0 ?
supportedRanges[0] : nil;
// Find the largest supported framerate less than capability maxFPS.
for (AVFrameRateRange* range in supportedRanges) {
if (range.maxFrameRate <= _capability.maxFPS &&
targetRange.maxFrameRate <= range.maxFrameRate) {
targetRange = range;
}
}
if (targetRange && [inputDevice lockForConfiguration:NULL]) {
inputDevice.activeVideoMinFrameDuration = targetRange.minFrameDuration;
inputDevice.activeVideoMaxFrameDuration = targetRange.minFrameDuration;
[inputDevice unlockForConfiguration];
}
}
_connection = [currentOutput connectionWithMediaType:AVMediaTypeVideo];
[self setRelativeVideoOrientation];
// finished configuring, commit settings to AVCaptureSession.
[_captureSession commitConfiguration];
[_captureSession startRunning];
[self signalCaptureChangeEnd];
}
- (void)setRelativeVideoOrientation {
if (!_connection.supportsVideoOrientation) {
return;
}
switch ([UIDevice currentDevice].orientation) {
case UIDeviceOrientationPortrait:
_connection.videoOrientation =
AVCaptureVideoOrientationPortrait;
break;
case UIDeviceOrientationPortraitUpsideDown:
_connection.videoOrientation =
AVCaptureVideoOrientationPortraitUpsideDown;
break;
case UIDeviceOrientationLandscapeLeft:
_connection.videoOrientation =
AVCaptureVideoOrientationLandscapeRight;
break;
case UIDeviceOrientationLandscapeRight:
_connection.videoOrientation =
AVCaptureVideoOrientationLandscapeLeft;
break;
case UIDeviceOrientationFaceUp:
case UIDeviceOrientationFaceDown:
case UIDeviceOrientationUnknown:
if (!_orientationHasChanged) {
_connection.videoOrientation =
AVCaptureVideoOrientationPortrait;
}
break;
}
}
- (void)onVideoError:(NSNotification*)notification {
NSLog(@"onVideoError: %@", notification);
// TODO(sjlee): make the specific error handling with this notification.
WEBRTC_TRACE(kTraceError,
kTraceVideoCapture,
_captureId,
"%s:%s:%d [AVCaptureSession startRunning] error.",
__FILE__,
__FUNCTION__,
__LINE__);
}
- (BOOL)stopCapture {
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
_orientationHasChanged = NO;
[self waitForCaptureChangeToFinish];
[self directOutputToNil];
if (!_captureSession) {
return NO;
}
_captureChanging = YES;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^(void) { [self stopCaptureInBackground]; });
return YES;
}
- (void)stopCaptureInBackground {
[_captureSession stopRunning];
[self signalCaptureChangeEnd];
}
- (BOOL)changeCaptureInputByUniqueId:(NSString*)uniqueId {
[self waitForCaptureChangeToFinish];
NSArray* currentInputs = [_captureSession inputs];
// remove current input
if ([currentInputs count] > 0) {
AVCaptureInput* currentInput =
(AVCaptureInput*)[currentInputs objectAtIndex:0];
[_captureSession removeInput:currentInput];
}
// Look for input device with the name requested (as our input param)
// get list of available capture devices
int captureDeviceCount = [DeviceInfoIosObjC captureDeviceCount];
if (captureDeviceCount <= 0) {
return NO;
}
AVCaptureDevice* captureDevice =
[DeviceInfoIosObjC captureDeviceForUniqueId:uniqueId];
if (!captureDevice) {
return NO;
}
// now create capture session input out of AVCaptureDevice
NSError* deviceError = nil;
AVCaptureDeviceInput* newCaptureInput =
[AVCaptureDeviceInput deviceInputWithDevice:captureDevice
error:&deviceError];
if (!newCaptureInput) {
const char* errorMessage = [[deviceError localizedDescription] UTF8String];
WEBRTC_TRACE(kTraceError,
kTraceVideoCapture,
_captureId,
"%s:%s:%d deviceInputWithDevice error:%s",
__FILE__,
__FUNCTION__,
__LINE__,
errorMessage);
return NO;
}
// try to add our new capture device to the capture session
[_captureSession beginConfiguration];
BOOL addedCaptureInput = NO;
if ([_captureSession canAddInput:newCaptureInput]) {
[_captureSession addInput:newCaptureInput];
addedCaptureInput = YES;
} else {
addedCaptureInput = NO;
}
[_captureSession commitConfiguration];
return addedCaptureInput;
}
- (void)captureOutput:(AVCaptureOutput*)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection*)connection {
const int kFlags = 0;
CVImageBufferRef videoFrame = CMSampleBufferGetImageBuffer(sampleBuffer);
if (CVPixelBufferLockBaseAddress(videoFrame, kFlags) != kCVReturnSuccess) {
return;
}
const int kYPlaneIndex = 0;
const int kUVPlaneIndex = 1;
uint8_t* baseAddress =
(uint8_t*)CVPixelBufferGetBaseAddressOfPlane(videoFrame, kYPlaneIndex);
size_t yPlaneBytesPerRow =
CVPixelBufferGetBytesPerRowOfPlane(videoFrame, kYPlaneIndex);
size_t yPlaneHeight = CVPixelBufferGetHeightOfPlane(videoFrame, kYPlaneIndex);
size_t uvPlaneBytesPerRow =
CVPixelBufferGetBytesPerRowOfPlane(videoFrame, kUVPlaneIndex);
size_t uvPlaneHeight =
CVPixelBufferGetHeightOfPlane(videoFrame, kUVPlaneIndex);
size_t frameSize =
yPlaneBytesPerRow * yPlaneHeight + uvPlaneBytesPerRow * uvPlaneHeight;
VideoCaptureCapability tempCaptureCapability;
tempCaptureCapability.width = CVPixelBufferGetWidth(videoFrame);
tempCaptureCapability.height = CVPixelBufferGetHeight(videoFrame);
tempCaptureCapability.maxFPS = _capability.maxFPS;
tempCaptureCapability.rawType = kVideoNV12;
_owner->IncomingFrame(baseAddress, frameSize, tempCaptureCapability, 0);
CVPixelBufferUnlockBaseAddress(videoFrame, kFlags);
}
- (void)signalCaptureChangeEnd {
[_captureChangingCondition lock];
_captureChanging = NO;
[_captureChangingCondition signal];
[_captureChangingCondition unlock];
}
- (void)waitForCaptureChangeToFinish {
[_captureChangingCondition lock];
while (_captureChanging) {
[_captureChangingCondition wait];
}
[_captureChangingCondition unlock];
}
@end