媒體捕獲

41553175912_.pic.jpg

AV Foundation捕捉會(huì)話的核心是AVCaptureSession。一個(gè)捕捉會(huì)話相當(dāng)于一個(gè)虛擬的“插線板”,用于連接輸入和輸出的資源。捕捉會(huì)話管理從設(shè)備得到的數(shù)據(jù)流,比如攝像頭和麥克風(fēng)設(shè)備,輸出一個(gè)或多個(gè)目的地。

捕捉設(shè)備

AVCaptureDevice是諸如攝像頭和麥克風(fēng)等物理設(shè)備定義了一個(gè)接口。AVCaptureDevice針對(duì)物理硬件設(shè)備定義了大量的控制方法,比如攝像頭的對(duì)焦、曝光、白平衡和閃光燈等。

捕捉設(shè)備的輸入

AVCaptureDevice需要包裝成AVCaptureDeviceInput實(shí)例來與AVCaptureSession進(jìn)行對(duì)接

捕捉設(shè)備的輸出

上圖AVCaptureSession的右邊即設(shè)備的輸出。

捕捉預(yù)覽

與視頻播放類似預(yù)覽層也是一個(gè)有Core Animation的CALayer的子類滿足的----AVCaptureVideoPreviewLayer.

代碼部分

創(chuàng)建捕捉控制器

THCameraController.h

//引入AVFoundation頭文件
#import <AVFoundation/AVFoundation.h>

extern NSString *const THThumbnailCreatedNotification;

@protocol THCameraControllerDelegate <NSObject>                             // 1 定義了當(dāng)有錯(cuò)誤發(fā)生時(shí),委托代理的方法
- (void)deviceConfigurationFailedWithError:(NSError *)error;
- (void)mediaCaptureFailedWithError:(NSError *)error;
- (void)assetLibraryWriteFailedWithError:(NSError *)error;
@end

@interface THCameraController : NSObject

@property (weak, nonatomic) id<THCameraControllerDelegate> delegate;
@property (nonatomic, strong, readonly) AVCaptureSession *captureSession;

// 配置會(huì)話                                                 // 2 配置會(huì)話 傳入一個(gè)error對(duì)象的指針的指針,一旦出錯(cuò)外界可以直接獲取錯(cuò)誤信息
- (BOOL)setupSession:(NSError **)error;
- (void)startSession;
- (void)stopSession;

// 攝像頭相關(guān)                                                // 3
- (BOOL)switchCameras;      //切換攝像頭
- (BOOL)canSwitchCameras;  // 能否切換攝像頭
@property (nonatomic, readonly) NSUInteger cameraCount;  //能用的攝像頭的個(gè)數(shù)
@property (nonatomic, readonly) BOOL cameraHasTorch;  //是否支持手電筒模式
@property (nonatomic, readonly) BOOL cameraHasFlash; //是否支持閃光燈模式
@property (nonatomic, readonly) BOOL cameraSupportsTapToFocus;//是否支持聚焦
@property (nonatomic, readonly) BOOL cameraSupportsTapToExpose; // 是否支持曝光
@property (nonatomic) AVCaptureTorchMode torchMode; //手電筒模式
@property (nonatomic) AVCaptureFlashMode flashMode; //閃光燈模式
/** Media Capture Methods **/                            // 5 捕捉圖片和視頻
- (void)captureStillImage;
// Video Recording
- (void)startRecording;
- (void)stopRecording;
- (BOOL)isRecording;
- (CMTime)recordedDuration;

@end

@interface THCameraController () <AVCaptureFileOutputRecordingDelegate>
@property (strong, nonatomic) AVCaptureSession *captureSession;
@property (weak, nonatomic) AVCaptureDeviceInput *activeVideoInput;
@property (strong, nonatomic) AVCaptureStillImageOutput *imageOutput;
@property (strong, nonatomic) AVCaptureMovieFileOutput *movieOutput;
@property (strong, nonatomic) NSURL *outputURL;
@end

@implementation THCameraController
- (BOOL)setupSession:(NSError **)error{

    self.captureSession = [[AVCaptureSession alloc] init];                  // 1創(chuàng)建會(huì)話
    //設(shè)置預(yù)設(shè)值
    self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;
    // Set up default camera device
    AVCaptureDevice *videoDevice =                                          // 2得到一個(gè)默認(rèn)視頻捕捉設(shè)備的的實(shí)例
        [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

    AVCaptureDeviceInput *videoInput =                                      // 3 把AVCaptureDevice包裝成AVCaptureDeviceInput對(duì)象添加到會(huì)話中
        [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
    if (videoInput) {
        if ([self.captureSession canAddInput:videoInput]) {          // 4是否可以被添加到會(huì)話中,如果可以則添加
            [self.captureSession addInput:videoInput];
            self.activeVideoInput = videoInput;
        }
    } else {
        return NO;
    }
    AVCaptureDevice *audioDevice =                                    // 5 獲取音頻設(shè)備的指針
        [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];

    AVCaptureDeviceInput *audioInput =                          // 6 包裝audioDevice的輸入
        [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:error];
    if (audioInput) {
        if ([self.captureSession canAddInput:audioInput]) {     // 7是否可以被添加到會(huì)話中,如果可以則添加
            [self.captureSession addInput:audioInput];
        }
    } else {
        return NO;
    }
    self.imageOutput = [[AVCaptureStillImageOutput alloc] init];            // 8 創(chuàng)建一個(gè)AVCaptureStillImageOutput實(shí)例,用于從攝像頭中捕捉靜態(tài)圖片
    self.imageOutput.outputSettings = @{AVVideoCodecKey : AVVideoCodecJPEG};

    if ([self.captureSession canAddOutput:self.imageOutput]) {
        [self.captureSession addOutput:self.imageOutput];
    }
    self.movieOutput = [[AVCaptureMovieFileOutput alloc] init];             // 9 創(chuàng)建一個(gè)AVCaptureMovieFileOutput實(shí)例用于輸出視頻文件。

    if ([self.captureSession canAddOutput:self.movieOutput]) { //同樣方式添加輸出
        [self.captureSession addOutput:self.movieOutput];
    }

    return YES;

}

- (void)startSession {
    if (![self.captureSession isRunning]) {                                 // 10 開始會(huì)話 異步調(diào)用不會(huì)阻塞主線程
        dispatch_async([self globalQueue], ^{
            [self.captureSession startRunning];
        });
    }
}

- (void)stopSession {
    if ([self.captureSession isRunning]) {                                  // 11 停止會(huì)話
        dispatch_async([self globalQueue], ^{
            [self.captureSession stopRunning];
        });
    }
}
//返回一個(gè)隊(duì)列
- (dispatch_queue_t)globalQueue {
    return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
}

// 攝像頭
- (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position { // 12返回指定位置的攝像頭
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *device in devices) {                              // 遍歷可用的視頻設(shè)備并返回對(duì)對(duì)應(yīng)的設(shè)備
        if (device.position == position) {
            return device;
        }
    }
    return nil;
}

- (AVCaptureDevice *)activeCamera {                                         // 13 返回當(dāng)前激活的視頻設(shè)備
    return self.activeVideoInput.device;
}

- (AVCaptureDevice *)inactiveCamera {                                       // 14 返回當(dāng)前未激活的設(shè)備
    AVCaptureDevice *device = nil;
    if (self.cameraCount > 1) {
        if ([self activeCamera].position == AVCaptureDevicePositionBack) {  // 
            device = [self cameraWithPosition:AVCaptureDevicePositionFront];
        } else {
            device = [self cameraWithPosition:AVCaptureDevicePositionBack];
        }
    }
    return device;
}

- (BOOL)canSwitchCameras {                                                  // 15 返回可用視頻捕捉設(shè)備
    return self.cameraCount > 1;
}

- (NSUInteger)cameraCount {                                                 // 
    return [[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count];
}

//切換攝像頭
- (BOOL)switchCameras {

    if (![self canSwitchCameras]) {                                         // 16 是否可以切換攝像頭(攝像頭至少多余1個(gè))
        return NO;
    }

    NSError *error;
    AVCaptureDevice *videoDevice = [self inactiveCamera];                   // 17 獲取為激活攝像頭指針,并包裝一個(gè)AVCaptureDeviceInput輸入

    AVCaptureDeviceInput *videoInput =
    [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];

    if (videoInput) {
        [self.captureSession beginConfiguration];                           // 18 開始設(shè)置會(huì)話

        [self.captureSession removeInput:self.activeVideoInput];            // 19 移除正在使用的激活攝像頭

        if ([self.captureSession canAddInput:videoInput]) {                 // 20 添加17不創(chuàng)建的新的AVCaptureDeviceInput輸入
            [self.captureSession addInput:videoInput];
            self.activeVideoInput = videoInput;
        } else {
            [self.captureSession addInput:self.activeVideoInput];
        }

        [self.captureSession commitConfiguration];                          // 21 提交設(shè)置  18-21步固定步驟, 先鎖住 然后切換 自后提交配置

    } else {
        [self.delegate deviceConfigurationFailedWithError:error];           // 22 錯(cuò)誤回調(diào)
        return NO;
    }

    return YES;
}

// 閃光 和 手電筒模式

- (BOOL)cameraHasFlash {
    return [[self activeCamera] hasFlash];
}

- (AVCaptureFlashMode)flashMode {
    return [[self activeCamera] flashMode];
}

// 設(shè)置閃光燈模式
- (void)setFlashMode:(AVCaptureFlashMode)flashMode {

    AVCaptureDevice *device = [self activeCamera];

    if (device.flashMode != flashMode &&
        [device isFlashModeSupported:flashMode]) {

        NSError *error;
        if ([device lockForConfiguration:&error]) {  // 鎖定設(shè)置
            device.flashMode = flashMode;            // 解鎖設(shè)置
            [device unlockForConfiguration];         // 解鎖設(shè)置
        } else {
            [self.delegate deviceConfigurationFailedWithError:error];
        }
    }
}

- (BOOL)cameraHasTorch {
    return [[self activeCamera] hasTorch];
}

- (AVCaptureTorchMode)torchMode {
    return [[self activeCamera] torchMode];
}

// 設(shè)置手電筒模式

- (void)setTorchMode:(AVCaptureTorchMode)torchMode {

    AVCaptureDevice *device = [self activeCamera];

    if (device.torchMode != torchMode &&
        [device isTorchModeSupported:torchMode]) {

        NSError *error;
        if ([device lockForConfiguration:&error]) {
            device.torchMode = torchMode;
            [device unlockForConfiguration];
        } else {
            [self.delegate deviceConfigurationFailedWithError:error];
        }
    }
}
// 獲取靜態(tài)圖片
- (void)captureStillImage {

    AVCaptureConnection *connection =                                   
        [self.imageOutput connectionWithMediaType:AVMediaTypeVideo];

    if (connection.isVideoOrientationSupported) {                       
        connection.videoOrientation = [self currentVideoOrientation];
    }

    id handler = ^(CMSampleBufferRef sampleBuffer, NSError *error) {
        if (sampleBuffer != NULL) {

            NSData *imageData =
                [AVCaptureStillImageOutput
                    jpegStillImageNSDataRepresentation:sampleBuffer];

            UIImage *image = [[UIImage alloc] initWithData:imageData];
                                  // 1

        } else {
            NSLog(@"NULL sampleBuffer: %@", [error localizedDescription]);
        }
    };
    // Capture still image   一個(gè)異步方法
    [self.imageOutput captureStillImageAsynchronouslyFromConnection:connection
                                                  completionHandler:handler];
}
// 錄制視頻部分
- (BOOL)isRecording {                                                       // 
    return self.movieOutput.isRecording;
}

- (void)startRecording {

    if (![self isRecording]) {

        AVCaptureConnection *videoConnection =                              // 獲取AVCaptureConnection指針
            [self.movieOutput connectionWithMediaType:AVMediaTypeVideo];

        if ([videoConnection isVideoOrientationSupported]) {                // 
            videoConnection.videoOrientation = self.currentVideoOrientation;
        }

        if ([videoConnection isVideoStabilizationSupported]) {              // 
            
            if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) {
                videoConnection.enablesVideoStabilizationWhenAvailable = YES;  // 是否支持視頻穩(wěn)定
            } else {
                videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
            }
        }

        AVCaptureDevice *device = [self activeCamera];

        if (device.isSmoothAutoFocusSupported) {                            // 攝像頭平滑對(duì)焦操作
            NSError *error;
            if ([device lockForConfiguration:&error]) {
                device.smoothAutoFocusEnabled = NO;
                [device unlockForConfiguration];
            } else {
                [self.delegate deviceConfigurationFailedWithError:error];
            }
        }

        self.outputURL = [self uniqueURL];                                  // 
        [self.movieOutput startRecordingToOutputFileURL:self.outputURL      //  設(shè)置代理和視頻存放路徑 開始錄制
                                      recordingDelegate:self];

    }
}

- (CMTime)recordedDuration {
    return self.movieOutput.recordedDuration;
}

- (NSURL *)uniqueURL {                                                      // 

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *dirPath =
        [fileManager temporaryDirectoryWithTemplateString:@"kamera.XXXXXX"];

    if (dirPath) {
        NSString *filePath =
            [dirPath stringByAppendingPathComponent:@"kamera_movie.mov"];
        return [NSURL fileURLWithPath:filePath];
    }

    return nil;
}

- (void)stopRecording {                                                     //  停止錄制
    if ([self isRecording]) {
        [self.movieOutput stopRecording];
    }
}

#pragma mark - AVCaptureFileOutputRecordingDelegate

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
      fromConnections:(NSArray *)connections
                error:(NSError *)error {
    if (error) {                                                            
        [self.delegate mediaCaptureFailedWithError:error];
    } else {
        [self writeVideoToAssetsLibrary:[self.outputURL copy]];
    }
    self.outputURL = nil;
}

- (void)writeVideoToAssetsLibrary:(NSURL *)videoURL {

    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];              // 

    if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:videoURL]) {   // 

        ALAssetsLibraryWriteVideoCompletionBlock completionBlock;

        completionBlock = ^(NSURL *assetURL, NSError *error){               // 
            if (error) {
                [self.delegate assetLibraryWriteFailedWithError:error];
            } 
        };

        [library writeVideoAtPathToSavedPhotosAlbum:videoURL                // 寫入資源庫中
                                    completionBlock:completionBlock];
    }
}
@end

預(yù)覽層

AVCaptureVideoPreviewLayer需要相關(guān)的session(AVCaptureSession)才能激活。

//空間坐標(biāo)轉(zhuǎn)換的方法。
- (CGPoint)captureDevicePointForPoint:(CGPoint)point {                      
    AVCaptureVideoPreviewLayer *layer =
        (AVCaptureVideoPreviewLayer *)self.layer;
    return [layer captureDevicePointOfInterestForPoint:point];
}
// 重寫layerClass方法 返回預(yù)覽層
+ (Class)layerClass {
    return [AVCaptureVideoPreviewLayer class];
}
- (AVCaptureSession*)session {
    return [(AVCaptureVideoPreviewLayer*)self.layer session];
}
// 設(shè)置session會(huì)話,關(guān)鍵layer和session。激活相應(yīng)的layer
- (void)setSession:(AVCaptureSession *)session {
    [(AVCaptureVideoPreviewLayer*)self.layer setSession:session];
}
AVCaptureVideoPreviewLayer的兩個(gè)坐標(biāo)轉(zhuǎn)換的方法。
獲取屏幕坐標(biāo)系數(shù)據(jù) 返回得到額設(shè)備坐標(biāo)系數(shù)據(jù)
- (CGPoint)captureDevicePointOfInterestForPoint:(CGPoint)pointInLayer;
獲取攝像頭坐標(biāo)系的數(shù)據(jù),返回轉(zhuǎn)換得到的屏幕坐標(biāo)系數(shù)據(jù)
- (CGPoint)pointForCaptureDevicePointOfInterest:(CGPoint)captureDevicePointOfInterest;

AV Foundation開發(fā)秘籍源碼

?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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