1. 程式人生 > Android開發 >iOS音視訊(一) -- AVFoundation捕捉

iOS音視訊(一) -- AVFoundation捕捉

一、AVFoundation簡介

AVFoundation 是蘋果在8.0之後推出的一個音視訊框架.

AVFoundation 最強大的功能是對 照片&視訊 的捕捉功能. 例如一些APP中的小視訊、直播等,可以通過AVFoundation來進行實現捕捉.

二、AVFoundation常用類

2.1、捕捉會話

捕捉會話主要是用到AVCaptureSession類, 它類似於一個排插, 各種裝置都需要與捕捉會話關聯起來。

2.2、捕捉裝置

通過AVCaptureDevice可以獲取到手機的各種硬體裝置, 例如: 麥克風、前後攝像頭、閃光燈等。

2.3、捕捉裝置輸入

通過AVCaptureDeviceInput可以捕捉到裝置的輸入。

在AVFoundation中, 捕捉裝置輸入是無法直接新增到Session中的, 所以需要將捕捉裝置輸入轉化為捕捉裝置新增進會話中。

2.4、捕捉裝置輸出

有輸入就有輸出。 在iOS10.0之後, 可以通過AVCapturePhotoOutput來進行獲取圖片的輸出, 通過AVCaptureMovieFileOutput來進行視訊檔案的輸出。 還有AVCaptureAudioDataOutput、還有AVCaptureVideoDataOutput等。

2.5、捕捉連線

AVCaptureConnection 可以根據捕捉的媒體的型別來建立一個連線

2.6、捕捉預覽

AVCaptureVideoPreviewLayer主要是一個圖層,主要是用來顯示攝像頭實時捕捉的內容。

三、AVFoundation的簡單使用

這裡涉及到攝像頭、麥克風、相簿, 需要配置使用者隱私需求。

3.1、配置會話

  1. 建立session會話
  2. 設定解析度
  3. 建立捕捉裝置
  4. 將捕捉裝置轉化為捕捉裝置輸入
  5. 將捕捉裝置輸入新增到會話中(新增過程需要注意是否能夠新增進會話中)
  6. 配置捕捉裝置的輸出
  7. 將捕捉裝置輸出新增到會話中(新增過程需要注意是否能夠新增進會話中)
#pragma mark - session設定
/// 配置session
/// @param error 錯誤回撥
- (BOOL)setupSession:(NSError **)error {
    /**
     * 新增視訊的輸入類別
     */
//初始化 self.captureSession = [[AVCaptureSession alloc] init]; //設定解析度 self.captureSession.sessionPreset = AVCaptureSessionPresetHigh; //拿到預設視訊捕捉裝置: iOS預設後置攝像頭為預設視訊捕捉色別 AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; //一定要將捕捉裝置轉化AVCaptureDeviceInput //注意: 為session新增捕捉裝置,必須將此封裝成AVCaptureDeviceInput物件 AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error]; if (videoInput) { //攝像頭不隸屬於任何一個app,是公共裝置,需要判斷是否能新增 if ([self.captureSession canAddInput:videoInput]) { [self.captureSession addInput:videoInput]; self.activeVideoInput = videoInput;//攝像頭分前置後置,需要儲存做切換操作 } } else { return NO; } /** * 新增音訊的輸入裝置 */ //新增音訊輸入裝置: 麥克風 AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:error]; if (audioInput) { if ([self.captureSession canAddInput:audioInput]) { [self.captureSession addInput:audioInput]; //音訊輸入只有麥克風,無需儲存 } } else { return NO; } /** * 設定輸出 (照片/視訊檔案) */ //圖片 self.imageOutput = [[AVCapturePhotoOutput alloc] init]; if ([self.captureSession canAddOutput:self.imageOutput]) { [self.captureSession addOutput:self.imageOutput]; } //視訊AVCaptureMovieFileOutput例項,QuickTime self.movieOutput = [[AVCaptureMovieFileOutput alloc] init]; if ([self.captureSession canAddOutput:self.movieOutput]) { [self.captureSession addOutput:self.movieOutput]; } //視訊佇列 self.videoQueue = dispatch_queue_create("glen.videoQueue",NULL); return YES;; } 複製程式碼

配置完捕捉會話之後,就需要通過外界的按鈕點選等操作來告訴AVFoundation來開啟或停止捕捉會話。

/// 啟動捕捉
- (void)startSession {
    if (![self.captureSession isRunning]) {
        dispatch_async(self.videoQueue,^{
            [self.captureSession startRunning];
        });
    }
}
/// 停止捕捉
- (void)stopSession {
    if ([self.captureSession isRunning]) {
        dispatch_async(self.videoQueue,^{
            [self.captureSession stopRunning];
        });
    }
}
複製程式碼

3.2、攝像頭的切換

獲取當前裝置上可用的攝像頭裝置,並根據需求來獲得指定的攝像頭裝置

/// 尋找指定攝像頭
/// @param positon 指定攝像頭裝置
- (AVCaptureDevice *)cameraWithPositon:(AVCaptureDevicePosition)positon {
    
    AVCaptureDeviceDiscoverySession *captureDeviceDiscoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera]
                                      mediaType:AVMediaTypeVideo
                                      position:AVCaptureDevicePositionUnspecified];
    //獲取到所有裝置
    NSArray *captureDevices = [captureDeviceDiscoverySession devices];
    //遍歷裝置
    for (AVCaptureDevice *device in captureDevices) {
        if (device.position == positon) {
            return device;
        }
    }
    return nil;
}

複製程式碼

因為攝像頭有多個,所以必須要知道當前使用的是哪個攝像頭

/// 獲取當前活躍的攝像頭
- (AVCaptureDevice *)activeCamera {
    return self.activeVideoInput.device;
}

/// 獲取另外一個不活躍的攝像頭
- (AVCaptureDevice *)inactiveCamera {
    
    AVCaptureDevice *device = nil;
    if (self.cameraCount > 1) {
        
        if ([self activeCamera].position == AVCaptureDevicePositionBack) {
            //後置變前置
            device = [self cameraWithPositon:AVCaptureDevicePositionFront];
        } else if ([self activeCamera].position == AVCaptureDevicePositionFront) {
            //前置變後置
            device = [self cameraWithPositon:AVCaptureDevicePositionBack];
        }
    }
    return device;;
}

複製程式碼

在進行切換之前,必須要知道其他的攝像頭是否是一個可進行使用的狀態

/// 是否能切換攝像頭
- (BOOL)canSwitchCameras {
    return self.cameraCount > 1;
}
複製程式碼

接下來就是對攝像頭進行切換

/// 切換攝像頭
- (BOOL)switchCameras {
    
    //判斷是否能切換
    if (![self canSwitchCameras]) {
        return NO;
    }
    
    //獲取當前裝置的反向裝置(不活躍的攝像頭)
    AVCaptureDevice *device = [self inactiveCamera];
    
    //將device新增進AVCaptureDeviceInput
    NSError *error;
    AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
    
    //新增進會話中
    if (videoInput) {
        //標註原始配置要發生改變
        [self.captureSession beginConfiguration];
        
        //將原來的輸入裝置移除
        [self.captureSession removeInput:self.activeVideoInput];
        
        //判斷能否加入
        if ([self.captureSession canAddInput:videoInput]) {
            [self.captureSession addInput:videoInput];
            //活躍裝置更新
            self.activeVideoInput = videoInput;
        } else {
            //如果新裝置無法加入,則將原來的視訊輸入裝置新增進去
            [self.captureSession addInput:self.activeVideoInput];
        }
        
        //提交修改配置
        [self.captureSession commitConfiguration];
        
    } else {
        //如果錯誤! 裝置新增錯誤
        return NO;
    }
    return YES;
}
複製程式碼

3.3、聚焦

  1. 拿到當前裝置
  2. 需要判斷裝置是否支援對焦等。
  3. 配置時不能讓多個物件對他進行更改,所以鎖定該裝置
  4. 設定對焦點、對焦模式等。
  5. 解鎖裝置
/// 詢問當前活躍的攝像頭是否支援興趣點對焦
- (BOOL)cameraSupportsTapToFocus {
    return [[self activeCamera] isFocusPointOfInterestSupported];
}

/// 設定對焦
- (void)focusAtPoint:(CGPoint)point {
    
    AVCaptureDevice *device = [self activeCamera];
    
    //判斷該裝置是否支援興趣點對焦  是否支援自動對焦
    if (device.isFocusPointOfInterestSupported && [device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
        
        NSError *error;
        //因為配置時,不能讓多個物件對它進行修改,所以過程上鎖
        if ([device lockForConfiguration:&error]) {
            
            //聚焦位置
            device.focusPointOfInterest = point;
            
            //自動對焦模式
            device.focusMode = AVCaptureFocusModeAutoFocus;
            
            //修改完畢,解鎖
            [device unlockForConfiguration];
        } else {
            //裝置錯誤
            
        }
    }
}

複製程式碼

3.4、曝光

  1. 拿到當前活躍裝置
  2. 建立曝光mode
  3. 判斷裝置是否支援指定的模式
  4. 鎖定該裝置
  5. 設定曝光點、曝光模式
  6. 判斷是否支援鎖定曝光
    1. 支援則使用KVO去設定狀態
    2. 監聽回撥,獲取裝置
    3. 判斷是否支援曝光
    4. 移除觀察者,修改裝置的曝光模式
  7. 解鎖裝置
  8. 對外聲稱一個重設對焦曝光的介面
static const NSString *CameraAdjustingExposureContext;

/// 當前活躍攝像頭是否支援曝光
- (BOOL)cameraSupportsTapToExpose {
    return [[self activeCamera] isExposurePointOfInterestSupported];
}

- (void)exposeAtPoint:(CGPoint)point {
    
    //獲取活躍攝像頭
    AVCaptureDevice *device = [self activeCamera];
    //設定根據場景曝光
    AVCaptureExposureMode exposureMode = AVCaptureExposureModeContinuousAutoExposure;
    //活躍攝像頭是否支援曝光 並且支援’根據場景曝光‘這個模式
    if (device.isExposurePointOfInterestSupported && [device isExposureModeSupported:exposureMode]) {

        //過程鎖定
        NSError *error;
        if ([device lockForConfiguration:&error]) {
            //裝置曝光點
            device.exposurePointOfInterest = point;
            //設定曝光模式
            device.exposureMode = exposureMode;
            
            //是否支援鎖定曝光
            if ([device isExposureModeSupported:AVCaptureExposureModeLocked]) {
                //使用kvo確定裝置的adjustingExposure屬性狀態
                [device addObserver:self forKeyPath:@"adjustingExposure" options:NSKeyValueObservingOptionNew context:&CameraAdjustingExposureContext];
            }
            
            //解鎖
            [device unlockForConfiguration];

        }
        
    }
    
}

/// 觀察者回調
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    if (context == &CameraAdjustingExposureContext) {
        
        //獲取裝置
        AVCaptureDevice *device = (AVCaptureDevice *)object;
        //判斷裝置是否不再調整曝光等級,確認裝置的exposureMode是否可以設定為AVCaptureExposureModeLocked
        if (!device.isExposurePointOfInterestSupported && [device isExposureModeSupported:AVCaptureExposureModeLocked]) {
            
            //移除作為adjustingExposure 的self,就不會得到後續變更的通知
            [object removeObserver:self forKeyPath:@"adjustingExposure" context:&CameraAdjustingExposureContext];

            //
            dispatch_async(dispatch_get_main_queue(),^{
                if ([device lockForConfiguration:nil]) {
                    device.exposureMode = AVCaptureExposureModeLocked;
                    [device unlockForConfiguration];
                } else {
                    //裝置錯誤回撥
                }
            });
        } else {
            [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        }
    }
}

//重新設定對焦&曝光
- (void)resetFocusAndExposureModes {

    AVCaptureDevice *device = [self activeCamera];

    AVCaptureFocusMode focusMode = AVCaptureFocusModeContinuousAutoFocus;
    
    //獲取對焦興趣點 和 連續自動對焦模式 是否被支援
    BOOL canResetFocus = [device isFocusPointOfInterestSupported]&& [device isFocusModeSupported:focusMode];
    
    AVCaptureExposureMode exposureMode = AVCaptureExposureModeContinuousAutoExposure;
    
    //確認曝光度可以被重設
    BOOL canResetExposure = [device isFocusPointOfInterestSupported] && [device isExposureModeSupported:exposureMode];
    
    //回顧一下,捕捉裝置空間左上角(0,0),右下角(1,1) 中心點則(0.5,0.5)
    CGPoint centPoint = CGPointMake(0.5f,0.5f);
    
    NSError *error;
    
    //鎖定裝置,準備配置
    if ([device lockForConfiguration:&error]) {
        
        //焦點可設,則修改
        if (canResetFocus) {
            device.focusMode = focusMode;
            device.focusPointOfInterest = centPoint;
        }
        
        //曝光度可設,則設定為期望的曝光模式
        if (canResetExposure) {
            device.exposureMode = exposureMode;
            device.exposurePointOfInterest = centPoint;
            
        }
        
        //釋放鎖定
        [device unlockForConfiguration];
        
    }else
    {
        
        //裝置錯誤回撥
    }
    
}

複製程式碼

3.5、拍照

  1. 對外暴露拍照介面 captureStillImage
  2. 對圖片輸出設定setting和代理
  3. 通過代理獲取到圖片的data資料
  4. 將圖片的data資料轉化為UIImage
  5. 將UIImage通過PHPhotoLibrary儲存進手機相簿,並通知外部一個UIImage用作顯示略縮圖
#pragma mark - 拍照
- (void)captureStillImage {

    //捕捉到圖片儲存格式jpg
    NSDictionary *setDic = @{AVVideoCodecKey:AVVideoCodecTypeJPEG};
    AVCapturePhotoSettings *outputSettings = [AVCapturePhotoSettings photoSettingsWithFormat:setDic];
    [self.imageOutput capturePhotoWithSettings:outputSettings delegate:self];

}

//代理方法
- (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhoto:(AVCapturePhoto *)photo error:(nullable NSError *)error {

    //圖片資料
    NSData *imageData = photo.fileDataRepresentation;
    UIImage *image = [[UIImage alloc] initWithData:imageData];
    
    //將圖片寫入到Library
    [self writeImageToAssetsLibrary:image];
}


/// 寫入到相簿
/// @param image 圖片
- (void)writeImageToAssetsLibrary:(UIImage *)image {

    __block PHObjectPlaceholder *assetPlaceholder = nil;
    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
        PHAssetChangeRequest *changeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image];
        assetPlaceholder = changeRequest.placeholderForCreatedAsset;
    } completionHandler:^(BOOL success,NSError * _Nullable error) {
        NSLog(@"OK");
        
        dispatch_async(dispatch_get_main_queue(),^{
            NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
            [nc postNotificationName:ThumbnailCreatedNotification object:image];
        });
    
    }];

}

複製程式碼