Приветствую все Хабра-сообщество!
Сегодня я бы хотел на примере нового продукта — QCamplr, рассказать вам о том, как работать с камерой iOS-девайса.
В этом посте я рассмотрю базовые аспекты настройки камеры и получения изображения для последующей работы с ним.
Шаг 1: Импортируем фреймворки
Я уже начал использовать новые синтаксические возможности Objective-C в Xcode 5, именно поэтому
#import
был заменен на
@import
Для работы с камерой iOS, нам однозначно понадобится AVFoundation.framework, также нам могут понадобится возможности из CoreMedia, CoreVideo и ImageIO. Советую импортировать все эти фреймворки прямо сейчас, что бы потом не возникало никаких ошибок.
@import AVFoundation; @import CoreMedia; @import CoreVideo; @import ImageIO;
Шаг 2: Декларируем свойства и методы
@property (nonatomic, strong, readonly) AVCaptureSession *captureSession; @property (nonatomic, strong, readonly) AVCaptureDevice *captureDevice; @property (nonatomic, strong, readonly) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer; @property (nonatomic, strong, readonly) AVCaptureDeviceInput *captureDeviceInput; @property (nonatomic, strong, readonly) AVCaptureStillImageOutput *captureStillImageOutput; + (QCCameraManager *)sharedManager; - (void)setupCaptureSessionWithSessionPreset:(NSString *)sessionPreset captureDevice:(AVCaptureDevice *)captureDevice captureViewLayer:(CALayer *)captureViewLayer; - (void)captureStillImageWithCompletionHandler:(void (^)(UIImage *capturedStillImage))completionHandler; - (BOOL)toggleCaptureDevice; - (AVCaptureDevice *)captureDeviceWithPosition:(AVCaptureDevicePosition)captureDevicePosition; - (BOOL)configureFocusModeOnDevice:(AVCaptureDevice *)captureDevice withFocusMode:(AVCaptureFocusMode)focusMode focusPointOfInterest:(CGPoint)focusPointOfInterest; - (BOOL)configureExposureModeOnDevice:(AVCaptureDevice *)captureDevice withExposureMode:(AVCaptureExposureMode)exposureMode exposurePointOfInterest:(CGPoint)exposurePointOfInterest; - (BOOL)configureWhiteBalanceModeOnDevice:(AVCaptureDevice *)captureDevice withWhiteBalanceMode:(AVCaptureWhiteBalanceMode)whiteBalanceMode; - (BOOL)configureFlashModeOnDevice:(AVCaptureDevice *)captureDevice withFlashMode:(AVCaptureFlashMode)flashMode; - (BOOL)configureTorchModeOnDevice:(AVCaptureDevice *)captureDevice withTorchMode:(AVCaptureTorchMode)torchMode torchLevel:(CGFloat)torchLevel; - (BOOL)configureLowLightBoostOnDevice:(AVCaptureDevice *)captureDevice withLowLightBoostEnabled:(BOOL)lowLightBoostEnabled;
Все наши свойства имеют readonly keyword. Так как мы не хотим, чтобы кто-то изменял их напрямую, но при этом бывают ситуации, когда нам нужно быстро получить доступ к активной сессии или любому другому свойству из основного AVFoundation stack, который нужен для осуществления фотосъемки с камеры iOS-девайса.
Далее мы объявили 11 методов, о которых я расскажу подробней в следующих шагах.
Теперь можно смело открывать .m файл и начинать писать реализацию нашей камеры.
Шаг 3: Singleton
Дело в том, что iOS-девайс поддерживает только одну активную AVCaptureSession. Если вы попробуете создать и запустить несколько сессий одновременно, то увидите ошибку в Console. Так же есть масса моментов, когда нам требуется получить доступ к свойствам камеры из любого класса нашего приложения, именно поэтому мы будем создавать singleton. Мы поддерживаем ARC, поэтому singeton создаем вот так:
+ (QCCameraManager *)sharedManager { static dispatch_once_t dispatchOncePredicate; __strong static QCCameraManager *cameraManager = nil; dispatch_once(&dispatchOncePredicate, ^{ cameraManager = [[QCCameraManager alloc] init]; }); return cameraManager; }
Шаг 4: Создаем и настраиваем наш AVFoundation Stack
- (void)setupCaptureSessionWithSessionPreset:(NSString *)sessionPreset captureDevice:(AVCaptureDevice *)captureDevice captureViewLayer:(CALayer *)captureViewLayer { [self setCaptureSession:[[AVCaptureSession alloc] init]]; if([[self captureSession] canSetSessionPreset:sessionPreset]) { [[self captureSession] setSessionPreset:sessionPreset]; } else { [[self captureSession] setSessionPreset:AVCaptureSessionPresetHigh]; } [self setCaptureDevice:captureDevice]; [self setCaptureDeviceInput:[[AVCaptureDeviceInput alloc] initWithDevice:[self captureDevice] error:nil]]; if(![[[self captureSession] inputs] count]) { if([[self captureSession] canAddInput:[self captureDeviceInput]]) { [[self captureSession] addInput:[self captureDeviceInput]]; } } [self setCaptureStillImageOutput:[[AVCaptureStillImageOutput alloc] init]]; [[self captureStillImageOutput] setOutputSettings:[[NSDictionary alloc] initWithObjectsAndKeys:AVVideoCodecJPEG, AVVideoCodecKey, nil]]; if ([[self captureSession] canAddOutput:[self captureStillImageOutput]]) { [[self captureSession] addOutput:[self captureStillImageOutput]]; } [self configureWhiteBalanceModeOnDevice:[self captureDevice] withWhiteBalanceMode:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance]; [self configureLowLightBoostOnDevice:[self captureDevice] withLowLightBoostEnabled:YES]; [self setCaptureVideoPreviewLayer:[[AVCaptureVideoPreviewLayer alloc] initWithSession:[self captureSession]]]; [[self captureVideoPreviewLayer] setVideoGravity:AVLayerVideoGravityResizeAspectFill]; [[self captureVideoPreviewLayer] setBounds:[captureViewLayer bounds]]; [[self captureVideoPreviewLayer] setFrame:[captureViewLayer bounds]]; [captureViewLayer setMasksToBounds:YES]; [captureViewLayer insertSublayer:[self captureVideoPreviewLayer] atIndex:0]; }
Тут все довольно просто:
- Мы создаем сессию, и задаем ей preset, он влияет на качество и разрешение картинки, которое вы будете получать во время вывода картинки на экран и непосредственной съемки.
- Выбираем устройство которое будет использовано для съемки, в нашем случае по умолчанию это всегда задняя камера
- Далее мы должны присоединить input к нашей сессии. Он нам понадобится для того, чтобы выводить изображение с камеры на экран в реальном времени. Сессия поддерживает только один input, при этом количество output не ограничивается одним. Проверки типа «могу ли я присоединить этот input к сессии?» нужно делать всегда!
- Делаем примерно тоже самое, только теперь присоединяем output, он нам понадобится для того, чтобы осуществлять съемку и получать реальное изображение с камеры. Проверки типа «могу ли я присоединить этот output к сессии?» нужно делать, всегда!
- Далее, мы проводим небольшую конфигурацию тех опций камеры, к которым мы не даем доступ пользователю. Подробнее о них я расскажу чуть позже.
- Наконец мы создаем слой, на который и будет выводится картинка в реальном времени с нашего input девайса. Слой добавляем как sublayer слоя, который мы передали как аргумент к этому методу.
Сессия создана! Чтобы ее запустить, нужно вызвать метод startRunning у свойства captureSession, для того чтобы прервать сессию, нужно вызвать метод stopRunning.
Вот так может выглядеть вызов данного метода в вашем контролере:
[[QCCameraManager sharedManager] setupCaptureSessionWithSessionPreset:AVCaptureSessionPresetPhoto captureDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo] captureViewLayer:[[self view] layer]]; [[[[QCCameraManager sharedManager] captureSession] startRunning];
Шаг 5: Настраиваем камеру
- (BOOL)configureTorchModeOnDevice:(AVCaptureDevice *)captureDevice withTorchMode:(AVCaptureTorchMode)torchMode torchLevel:(CGFloat)torchLevel { if([captureDevice hasTorch] && [captureDevice isTorchAvailable]) { if([captureDevice torchMode] != torchMode) { if([captureDevice isTorchModeSupported:torchMode]) { if(!(([captureDevice isTorchActive]) && (torchMode == AVCaptureTorchModeOn))) { if([captureDevice lockForConfiguration:nil]) { if((torchMode == AVCaptureTorchModeOn) && (torchLevel >= 0.0f)) { [captureDevice setTorchModeOnWithLevel:torchLevel error:nil]; } else { [captureDevice setTorchMode:torchMode]; } [captureDevice unlockForConfiguration]; } else { return NO; } } } else { return NO; } } return YES; } else { return NO; } } - (BOOL)configureFlashModeOnDevice:(AVCaptureDevice *)captureDevice withFlashMode:(AVCaptureFlashMode)flashMode { if([captureDevice isFlashAvailable] && [captureDevice isFlashModeSupported:flashMode]) { if([captureDevice flashMode] != flashMode) { if([captureDevice lockForConfiguration:nil]) { [captureDevice setFlashMode:flashMode]; [captureDevice unlockForConfiguration]; } else { return NO; } } return YES; } else { return NO; } } - (BOOL)configureWhiteBalanceModeOnDevice:(AVCaptureDevice *)captureDevice withWhiteBalanceMode:(AVCaptureWhiteBalanceMode)whiteBalanceMode { if([captureDevice isWhiteBalanceModeSupported:whiteBalanceMode]) { if([captureDevice whiteBalanceMode] != whiteBalanceMode) { if([captureDevice lockForConfiguration:nil]) { [captureDevice setWhiteBalanceMode:whiteBalanceMode]; [captureDevice unlockForConfiguration]; } else { return NO; } } return YES; } else { return NO; } } - (BOOL)configureFocusModeOnDevice:(AVCaptureDevice *)captureDevice withFocusMode:(AVCaptureFocusMode)focusMode focusPointOfInterest:(CGPoint)focusPointOfInterest { if([captureDevice isFocusModeSupported:focusMode] && [captureDevice isFocusPointOfInterestSupported]) { if([captureDevice focusMode] == focusMode) { if([captureDevice lockForConfiguration:nil]) { [captureDevice setFocusPointOfInterest:focusPointOfInterest]; [captureDevice setFocusMode:focusMode]; [captureDevice unlockForConfiguration]; } else { return NO; } } return YES; } else { return NO; } } - (BOOL)configureExposureModeOnDevice:(AVCaptureDevice *)captureDevice withExposureMode:(AVCaptureExposureMode)exposureMode exposurePointOfInterest:(CGPoint)exposurePointOfInterest { if ([captureDevice isExposureModeSupported:exposureMode] && [captureDevice isExposurePointOfInterestSupported]) { if([captureDevice exposureMode] == exposureMode) { if([captureDevice lockForConfiguration:nil]) { [captureDevice setExposurePointOfInterest:exposurePointOfInterest]; [captureDevice setExposureMode:exposureMode]; [captureDevice unlockForConfiguration]; } else { return NO; } } return YES; } else { return NO; } } - (BOOL)configureLowLightBoostOnDevice:(AVCaptureDevice *)captureDevice withLowLightBoostEnabled:(BOOL)lowLightBoostEnabled { if([captureDevice isLowLightBoostSupported]) { if([captureDevice isLowLightBoostEnabled] != lowLightBoostEnabled) { if([captureDevice lockForConfiguration:nil]) { [captureDevice setAutomaticallyEnablesLowLightBoostWhenAvailable:lowLightBoostEnabled]; [captureDevice unlockForConfiguration]; } else { return NO; } } return YES; } else { return NO; } }
Все возможные настройки камеры осуществляются нашими собственными методами, все они работают по одному и тому же принципу. Все что нужно знать, это то, что перед изменениям параметров нам нужно вызвать метод lockForConfiguration:, после того как мы завершили процесс конфигурации нужно вызвать метод unlockForConfiguration.
В QCamplr мы даем возможность пользователю настраивать 4 опции: вспышка, фонарь(ночная съемка), фокус и экспозиция.
Шаг 5: Фотографируем
- (void)captureStillImageWithCompletionHandler:(void (^)(UIImage *capturedStillImage))completionHandler { if(![[self captureStillImageOutput] isCapturingStillImage]) { [[NSNotificationCenter defaultCenter] postNotificationName:@"QCCameraManagedWillCaptureStillImageNotification" object:nil userInfo:nil]; [[self captureStillImageOutput] captureStillImageAsynchronouslyFromConnection:[[self captureStillImageOutput] connectionWithMediaType:AVMediaTypeVideo] completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) { if (imageDataSampleBuffer) { UIImage *capturedStillImage = [[UIImage alloc] initWithData:[AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer]]; completionHandler([capturedStillImage croppedImageFromCaptureDevice:[self captureDevice]]); } }]; } }
Этот метод вызывается тогда, когда вы нажимаете на большой красный «туглер» в QCamplr. В completionHandler приходит картинка в формате jpeg, завернутая в объект класса UIImage.
Теперь немного вспомогательных методов, которые помогут вам в работе с камерой.
Метод #1
Получить доступ к фронтальной или задней камере можно при помощи данного метода:
- (AVCaptureDevice *)captureDeviceWithPosition:(AVCaptureDevicePosition)captureDevicePosition { NSArray *captureDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; for(AVCaptureDevice *captureDevice in captureDevices) { if([captureDevice position] == captureDevicePosition) { return captureDevice; } } return nil; }
Метод #2
Переключиться с фронтальной камеры на заднюю и наоборот можно при помощи данного метода:
- (BOOL)toggleCaptureDevice { if ([[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count] > 1) { AVCaptureDeviceInput *captureDeviceInput = [self captureDeviceInput]; if([[[self captureDeviceInput] device] position] == AVCaptureDevicePositionBack) { [self setCaptureDeviceInput:[[AVCaptureDeviceInput alloc] initWithDevice:[self captureDeviceWithPosition:AVCaptureDevicePositionFront] error:nil]]; } else if([[[self captureDeviceInput] device] position] == AVCaptureDevicePositionFront) { [self setCaptureDeviceInput:[[AVCaptureDeviceInput alloc] initWithDevice:[self captureDeviceWithPosition:AVCaptureDevicePositionBack] error:nil]]; } else if([[[self captureDeviceInput] device] position] == AVCaptureDevicePositionUnspecified) { return NO; } [self setCaptureDevice:[[self captureDeviceInput] device]]; [[self captureSession] beginConfiguration]; [[self captureSession] removeInput:captureDeviceInput]; if([[self captureSession] canAddInput:[self captureDeviceInput]]) { [[self captureSession] addInput:[self captureDeviceInput]]; } else { [[self captureSession] addInput:captureDeviceInput]; } [[self captureSession] commitConfiguration]; return YES; } else { return NO; } }
Полезные Ссылки
Документация по AVFoundation
Официальный сайт QCamplr
P.S. Надеюсь, эта статья была полезна всем тем, кто хотел получить больше свободы и гибкости в работе с камерами устройства, но не знал, с чего начать. Желаю всем побольше качественного кода, и удачи в нашем нелегком деле!
ссылка на оригинал статьи http://habrahabr.ru/post/197580/
Добавить комментарий