iOS學(xué)習(xí)筆記27-攝像頭

一、攝像頭

在iOS中,手機(jī)攝像頭的使用有以下兩種方法:

  1. UIImagePickerController拍照和視頻錄制
  • 優(yōu)點(diǎn):使用方便,功能強(qiáng)大
  • 缺點(diǎn):高度封裝性,無(wú)法實(shí)現(xiàn)一些自定義工作
  1. AVFoundation框架實(shí)現(xiàn)
  • 優(yōu)點(diǎn):靈活性強(qiáng),提供了很多現(xiàn)成的輸入設(shè)備和輸出設(shè)備,還有很多底層的內(nèi)容可以供開(kāi)發(fā)者使用
  • 缺點(diǎn):需要和底層打交道,學(xué)習(xí)難度大,使用復(fù)雜

我們平常使用UIImagePickerController就基本可以滿足了,功能確實(shí)強(qiáng)大,但它也有不好的一點(diǎn),那就是由于它的高度封裝性,如果要進(jìn)行某些自定義工作就比較復(fù)雜,例如如果要做出一款類似于美顏相機(jī)的拍照界面就比較難以實(shí)現(xiàn),這個(gè)時(shí)候就要考慮AVFoundation框架實(shí)現(xiàn)。

二、UIImagePickerController

UIImagePickerController繼承于UINavigationController,屬于UIKit框架,可以實(shí)現(xiàn)圖片選取、拍照、錄制視頻等功能,使用起來(lái)十分方便。

1. 常用屬性:
@property (nonatomic) UIImagePickerControllerSourceType sourceType;/* 拾取源類型枚舉 */
typedef NS_ENUM(NSInteger, UIImagePickerControllerSourceType) {
    UIImagePickerControllerSourceTypePhotoLibrary,//照片庫(kù)
    UIImagePickerControllerSourceTypeCamera,//攝像頭
    UIImagePickerControllerSourceTypeSavedPhotosAlbum//相簿
};
/* 
  媒體類型,默認(rèn)情況下此數(shù)組包含kUTTypeImage,表示拍照
  如果要錄像,必須設(shè)置為kUTTypeVideo(視頻不帶聲音)或kUTTypeMovie(視頻帶聲音)
*/
@property (nonatomic,copy) NSArray<NSString *> *mediaTypes;
@property (nonatomic) NSTimeInterval videoMaximumDuration;//視頻最大錄制時(shí)長(zhǎng),默認(rèn)10s
@property (nonatomic) UIImagePickerControllerQualityType videoQuality;//視頻質(zhì)量
typedef NS_ENUM(NSInteger, UIImagePickerControllerQualityType) {
    UIImagePickerControllerQualityTypeHigh = 0,  //高清
    UIImagePickerControllerQualityTypeMedium,    //中等,適合WiFi傳輸
    UIImagePickerControllerQualityTypeLow,      //低質(zhì)量,適合蜂窩網(wǎng)傳輸
    UIImagePickerControllerQualityType640x480, //640*480
    UIImagePickerControllerQualityTypeIFrame1280x720, //1280*720
    UIImagePickerControllerQualityTypeIFrame960x540, //960*540
};

@property (nonatomic)  BOOL showsCameraControls;/* 是否顯示攝像頭控制面板,默認(rèn)為YES */
@property (nonatomic,strong) UIView *cameraOverlayView;/* 攝像頭上覆蓋的視圖 */
@property (nonatomic) CGAffineTransform cameraViewTransform;/* 攝像頭形變 */

@property (nonatomic) UIImagePickerControllerCameraCaptureMode cameraCaptureMode;/* 攝像頭捕捉模式 */
typedef NS_ENUM(NSInteger, UIImagePickerControllerCameraCaptureMode) {
    UIImagePickerControllerCameraCaptureModePhoto,//拍照模式
    UIImagePickerControllerCameraCaptureModeVideo//視頻錄制模式
};
@property (nonatomic) UIImagePickerControllerCameraDevice cameraDevice;/* 攝像頭設(shè)備 */
typedef NS_ENUM(NSInteger, UIImagePickerControllerCameraDevice) {
    UIImagePickerControllerCameraDeviceRear,//前置攝像頭
    UIImagePickerControllerCameraDeviceFront//后置攝像頭
};
@property (nonatomic) UIImagePickerControllerCameraFlashMode cameraFlashMode;/* 閃光燈模式 */
typedef NS_ENUM(NSInteger, UIImagePickerControllerCameraFlashMode) {
    UIImagePickerControllerCameraFlashModeOff  = -1,//關(guān)閉閃光燈
    UIImagePickerControllerCameraFlashModeAuto = 0,//閃光燈自動(dòng),默認(rèn)
    UIImagePickerControllerCameraFlashModeOn   = 1//打開(kāi)閃光燈
};
2. 常用對(duì)象方法:
- (void)takePicture; //拍照                     
- (BOOL)startVideoCapture;//開(kāi)始錄制視頻
- (void)stopVideoCapture;//停止錄制視頻
3. 代理方法:
/* 媒體獲取完成會(huì)調(diào)用 */
- (void)imagePickerController:(UIImagePickerController *)picker 
didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info;
/* 取消獲取會(huì)調(diào)用 */
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker;
4. 擴(kuò)展函數(shù),用于保存到相簿:
/* 保存圖片到相簿 */
void UIImageWriteToSavedPhotosAlbum(
    UIImage *image,//保存的圖片UIImage
    id completionTarget,//回調(diào)的執(zhí)行者
    SEL completionSelector, //回調(diào)方法
    void *contextInfo//回調(diào)參數(shù)信息
);
//上面一般保存圖片的回調(diào)方法為:
- (void)image:(UIImage *)image 
        didFinishSavingWithError:(NSError *)error 
        contextInfo:(void *)contextInfo;

/* 判斷是否能保存視頻到相簿 */
BOOL UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(NSString *videoPath);
/* 保存視頻到相簿 */
void UISaveVideoAtPathToSavedPhotosAlbum(
    NSString *videoPath, //保存的視頻文件路徑
    id completionTarget, //回調(diào)的執(zhí)行者
    SEL completionSelector,//回調(diào)方法
    void *contextInfo//回調(diào)參數(shù)信息
);
//上面一般保存視頻的回調(diào)方法為:
- (void)video:(NSString *)videoPath 
        didFinishSavingWithError:(NSError *)error 
        contextInfo:(void *)contextInfo;
5. 使用攝像頭的步驟:
  1. 創(chuàng)建UIImagePickerController對(duì)象
  1. 指定拾取源,拍照和錄像都需要使用攝像頭
  2. 指定攝像頭設(shè)備,是前置的還是后置的
  3. 設(shè)置媒體類型,媒體類型定義在MobileCoreServices.framework
  4. 指定攝像頭捕捉模式,錄像必須先設(shè)置媒體類型再設(shè)置捕捉模式。
  5. 展示UIImagePickerController,通常以模態(tài)彈出形式打開(kāi)
  6. 拍照或錄像結(jié)束后,在代理方法中展示或者保存照片或視頻
6. 下面是具體實(shí)例代碼:
#import "ViewController.h"
#import <MobileCoreServices/MobileCoreServices.h>

@interface ViewController () <UIImagePickerControllerDelegate,UINavigationControllerDelegate>
@property (strong, nonatomic) UIImagePickerController *pickerController;//拾取控制器
@property (strong, nonatomic) IBOutlet UIImageView *showImageView;//顯示圖片
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    //初始化拾取控制器
    [self initPickerController];
}
/* 初始化拾取控制器 */
- (void)initPickerController{
    //創(chuàng)建拾取控制器
    UIImagePickerController *pickerController = [[UIImagePickerController alloc] init];
    //設(shè)置拾取源為攝像頭
    pickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
    //設(shè)置攝像頭為后置
    pickerController.cameraDevice = UIImagePickerControllerCameraDeviceRear;
    pickerController.editing = YES;//設(shè)置運(yùn)行編輯,即可以點(diǎn)擊一些拾取控制器的控件
    pickerController.delegate = self;//設(shè)置代理
    self.pickerController = pickerController;
}
#pragma mark - UI點(diǎn)擊
/* 點(diǎn)擊拍照 */
- (IBAction)imagePicker:(id)sender {
    //設(shè)定拍照的媒體類型
    self.pickerController.mediaTypes = @[(NSString *)kUTTypeImage];
    //設(shè)置攝像頭捕捉模式為捕捉圖片
    self.pickerController.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
    //模式彈出拾取控制器
    [self presentViewController:self.pickerController animated:YES completion:nil];
}
/* 點(diǎn)擊錄像 */
- (IBAction)videoPicker:(id)sender {
    //設(shè)定錄像的媒體類型
    self.pickerController.mediaTypes = @[(NSString *)kUTTypeMovie];
    //設(shè)置攝像頭捕捉模式為捕捉視頻
    self.pickerController.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo;
    //設(shè)置視頻質(zhì)量為高清
    self.pickerController.videoQuality = UIImagePickerControllerQualityTypeHigh;
    //模式彈出拾取控制器
    [self presentViewController:self.pickerController animated:YES completion:nil];
}

#pragma mark - 代理方法
/* 拍照或錄像成功,都會(huì)調(diào)用 */
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
    //從info取出此時(shí)攝像頭的媒體類型
    NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];

    if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {//如果是拍照
        //獲取拍照的圖像
        UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
        //保存圖像到相簿
        UIImageWriteToSavedPhotosAlbum(image, self, 
                  @selector(image:didFinishSavingWithError:contextInfo:), nil);
        
    } else if ([mediaType isEqualToString:(NSString *)kUTTypeMovie]) {//如果是錄像
        //獲取錄像文件路徑URL
        NSURL *url = [info objectForKey:UIImagePickerControllerMediaURL];
        NSString *path = url.path;
        //判斷能不能保存到相簿
        if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path)) {
            //保存視頻到相簿
            UISaveVideoAtPathToSavedPhotosAlbum(path, self, 
                   @selector(video:didFinishSavingWithError:contextInfo:), nil);
        }
        
    }
    //拾取控制器彈回
    [self dismissViewControllerAnimated:YES completion:nil];
}
/* 取消拍照或錄像會(huì)調(diào)用 */
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    NSLog(@"取消");
    //拾取控制器彈回
    [self dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark - 保存圖片或視頻完成的回調(diào)
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error
                                            contextInfo:(void *)contextInfo {
    NSLog(@"保存圖片完成");
    self.showImageView.image = image;
    self.showImageView.contentMode = UIViewContentModeScaleToFill;
}

- (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error
                                                 contextInfo:(void *)contextInfo {
    NSLog(@"保存視頻完成");
}
@end


功能十分強(qiáng)大,基本滿足一般的需求,使用起來(lái)也很簡(jiǎn)單。

三、AVFoundation的拍照錄像

首先了解下AVFoundation做拍照和錄像的相關(guān)類:
  1. AVCaptureSession
    媒體捕捉會(huì)話,負(fù)責(zé)把捕獲到的音視頻數(shù)據(jù)輸出到輸出設(shè)備上,一個(gè)會(huì)話可以有多個(gè)輸入輸出。
  1. AVCaptureVideoPervieewLayer
    相機(jī)拍攝預(yù)覽圖層,是CALayer的子類,實(shí)時(shí)查看拍照或錄像效果。
  2. AVCaptureDevice
    輸入設(shè)備,包括麥克風(fēng)、攝像頭等,可以設(shè)置一些物理設(shè)備的屬性
  3. AVCaptureDeviceInput
    設(shè)備輸入數(shù)據(jù)管理對(duì)象,管理輸入數(shù)據(jù)
  4. AVCaptureOutput
    設(shè)備輸出數(shù)據(jù)管理對(duì)象,管理輸出數(shù)據(jù),通常使用它的子類:
AVCaptureAudioDataOutput//輸出音頻管理對(duì)象,輸出數(shù)據(jù)為NSData
AVCaptureStillImageDataOutput//輸出圖片管理對(duì)象,輸出數(shù)據(jù)為NSData
AVCaptureVideoDataOutput//輸出視頻管理對(duì)象,輸出數(shù)據(jù)為NSData
/* 輸出文件管理對(duì)象,輸出數(shù)據(jù)以文件形式輸出 */
AVCaptureFileOutput  
{//子類    
       AVCaptureAudioFileOutput   //輸出是音頻文件
       AVCaptureMovieFileOutput   //輸出是視頻文件
}
拍照或錄像的一般步驟為:
  1. 創(chuàng)建AVCaptureSession對(duì)象
  1. 使用AVCaptureDevice的類方法獲得要使用的設(shè)備
  2. 利用輸入設(shè)備AVCaptureDevice創(chuàng)建并初始化AVCaptureDeviceInput對(duì)象
  3. 初始化輸出數(shù)據(jù)管理對(duì)象,看具體輸出什么數(shù)據(jù)決定使用哪個(gè)AVCaptureOutput子類
  4. AVCaptureDeviceInput、AVCaptureOutput添加到媒體會(huì)話管理對(duì)象AVCaptureSession
  5. 創(chuàng)建視頻預(yù)覽圖層AVCaptureVideoPreviewLayer并指定媒體會(huì)話,添加圖層到顯示容器中
  6. 調(diào)用媒體會(huì)話AVCaptureSessionstartRunning方法開(kāi)始捕獲,stopRunning方法停止捕捉
  7. 將 捕獲的音頻或視頻數(shù)據(jù)輸出到指定文件

拍照使用實(shí)例如下:

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()
@property (strong, nonatomic) AVCaptureSession *session;//媒體管理會(huì)話
@property (strong, nonatomic) AVCaptureDeviceInput *captureInput;//輸入數(shù)據(jù)對(duì)象
@property (strong, nonatomic) AVCaptureStillImageOutput *imageOutput;//輸出數(shù)據(jù)對(duì)象
@property (strong, nonatomic) AVCaptureVideoPreviewLayer *captureLayer;//視頻預(yù)覽圖層
@property (strong, nonatomic) IBOutlet UIButton *captureBtn;//拍照按鈕
@property (strong, nonatomic) IBOutlet UIButton *openCaptureBtn;//打開(kāi)攝像頭按鈕

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self initCapture];
    self.openCaptureBtn.hidden = NO;
    self.captureBtn.hidden = YES;
}
/* 初始化攝像頭 */
- (void)initCapture{
    //1. 創(chuàng)建媒體管理會(huì)話
    AVCaptureSession *session = [[AVCaptureSession alloc] init];
    self.session = session;
    //判斷分辨率是否支持1280*720,支持就設(shè)置為1280*720
    if( [session canSetSessionPreset:AVCaptureSessionPreset1280x720] ) {
        session.sessionPreset = AVCaptureSessionPreset1280x720;
    }
    //2. 獲取后置攝像頭設(shè)備對(duì)象
    AVCaptureDevice *device = nil;
    NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *camera in cameras) {
        if (camera.position == AVCaptureDevicePositionBack) {//取得后置攝像頭
            device = camera;
        }
    }
    if(!device) {
        NSLog(@"取得后置攝像頭錯(cuò)誤");
        return;
    }
    //3. 創(chuàng)建輸入數(shù)據(jù)對(duì)象
    NSError *error = nil;
    AVCaptureDeviceInput *captureInput = [[AVCaptureDeviceInput alloc] initWithDevice:device
                                                                                error:&error];
    if (error) {
        NSLog(@"創(chuàng)建輸入數(shù)據(jù)對(duì)象錯(cuò)誤");
        return;
    }
    self.captureInput = captureInput;
    //4. 創(chuàng)建輸出數(shù)據(jù)對(duì)象
    AVCaptureStillImageOutput *imageOutput = [[AVCaptureStillImageOutput alloc] init];
    NSDictionary *setting = @{ AVVideoCodecKey:AVVideoCodecJPEG };
    [imageOutput setOutputSettings:setting];
    self.imageOutput = imageOutput;
    //5. 添加輸入數(shù)據(jù)對(duì)象和輸出對(duì)象到會(huì)話中
    if ([session canAddInput:captureInput]) {
        [session addInput:captureInput];
    }
    if ([session canAddOutput:imageOutput]) {
        [session addOutput:imageOutput];
    }
    //6. 創(chuàng)建視頻預(yù)覽圖層
    AVCaptureVideoPreviewLayer *videoLayer = 
              [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
    self.view.layer.masksToBounds = YES;
    videoLayer.frame = self.view.bounds;
    videoLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    //插入圖層在拍照按鈕的下方
    [self.view.layer insertSublayer:videoLayer below:self.captureBtn.layer];
    self.captureLayer = videoLayer;
}
#pragma mark - UI點(diǎn)擊
/* 點(diǎn)擊拍照按鈕 */
- (IBAction)takeCapture:(id)sender {
    //根據(jù)設(shè)備輸出獲得連接
    AVCaptureConnection *connection = [self.imageOutput connectionWithMediaType:AVMediaTypeVideo];
    //通過(guò)連接獲得設(shè)備輸出的數(shù)據(jù)
    [self.imageOutput captureStillImageAsynchronouslyFromConnection:connection
                      completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error)
    {
        //獲取輸出的JPG圖片數(shù)據(jù)
        NSData *imageData = 
              [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
        UIImage *image = [UIImage imageWithData:imageData];
        UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);//保存到相冊(cè)
        self.captureLayer.hidden = YES;
        self.captureBtn.hidden = YES;
        self.openCaptureBtn.hidden = NO;
        [self.session stopRunning];//停止捕捉
    }];
}
/* 點(diǎn)擊打開(kāi)攝像頭按鈕 */
- (IBAction)openCapture:(id)sender {
    self.captureLayer.hidden = NO;
    self.captureBtn.hidden = NO;
    self.openCaptureBtn.hidden = YES;
    [self.session startRunning];//開(kāi)始捕捉
}
@end

錄像的操作差不多,下面代碼是以上面代碼為基礎(chǔ)進(jìn)行修改:

  1. 比拍照多了一個(gè)音頻輸入,改變輸出數(shù)據(jù)對(duì)象的類
  1. 需要處理視頻輸出代理方法
  2. 錄制的方法是在輸出數(shù)據(jù)對(duì)象上
1. 獲取音頻輸入數(shù)據(jù)對(duì)象以及視頻輸出數(shù)據(jù)對(duì)象
//獲取麥克風(fēng)設(shè)備對(duì)象
AVCaptureDevice *device = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio].firstObject;
if(!device) {
    NSLog(@"取得麥克風(fēng)錯(cuò)誤");
    return;
}
//創(chuàng)建輸入數(shù)據(jù)對(duì)象
NSError *error = nil;
AVCaptureDeviceInput *audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:device
                                                                          error:&error];
if (error) {
    NSLog(@"創(chuàng)建輸入數(shù)據(jù)對(duì)象錯(cuò)誤");
    return;
}
//創(chuàng)建視頻文件輸出對(duì)象
AVCaptureMovieFileOutput *movieOutput = [[AVCaptureMovieFileOutput alloc] init];
self.movieOutput = movieOutput;
2. 添加進(jìn)媒體管理會(huì)話中
if([session canAddInput:captureInput]) {
    [session addInput:captureInput];
    [session addInput:audioInput];
    //添加防抖動(dòng)功能
    AVCaptureConnection *connection = [movieOutput connectionWithMediaType:AVMediaTypeVideo];
    if ([connection isVideoStabilizationSupported]) {
        connection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
    }
}
if ([session canAddOutput:movieOutput]) {
    [session addOutput:movieOutput];
}
3. 點(diǎn)擊錄像按鈕
if (!self.movieOutput.isRecording) {
    NSString *outputPath = [NSTemporaryDirectory() stringByAppendingString:@"myMovie.mov"];
    NSURL *url = [NSURL fileURLWithPath:outputPath];//記住是文件URL,不是普通URL
    //開(kāi)始錄制并設(shè)置代理監(jiān)控錄制過(guò)程,錄制文件會(huì)存放到指定URL路徑下
    [self.movieOutput startRecordingToOutputFileURL:url recordingDelegate:self];
} else {
    [self.movieOutput stopRecording];//結(jié)束錄制
}
4. 處理錄制代理AVCaptureFileOutputRecordingDelegate
/* 開(kāi)始錄制會(huì)調(diào)用 */
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
        didStartRecordingToOutputFileAtURL:(NSURL *)fileURL
                           fromConnections:(NSArray *)connections
{
    NSLog(@"開(kāi)始錄制");
}
/* 錄制完成會(huì)調(diào)用 */
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
        didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
                            fromConnections:(NSArray *)connections
                                      error:(NSError *)error
{
    NSLog(@"完成錄制");
    NSString *path = outputFileURL.path;
    //保存錄制視頻到相簿
    if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path)) {
        UISaveVideoAtPathToSavedPhotosAlbum(path, nil, nil, nil);
    }
}

四、iOS音頻視頻使用總結(jié)


以上的表格中,我沒(méi)有全部都講,我主要講AVFoundation框架,里面還有非常多的內(nèi)容可以學(xué)習(xí),這個(gè)框架是非常強(qiáng)大,有時(shí)間也可以再深入去學(xué)習(xí)。
iOS對(duì)于多媒體支持相當(dāng)靈活和完善,具體開(kāi)發(fā)過(guò)程到底如何選擇,以上的表格僅供參考。

代碼Demo點(diǎn)這里:LearnDemo里面的CaptureDemo

如果有什么問(wèn)題可以在下方評(píng)論區(qū)留言,我會(huì)積極響應(yīng)的!O(∩_∩)O哈!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容