iOS音視頻(二) -- AVFoundation的高級捕捉

1、視頻捕捉

1.一、實現QuickTime視頻的錄製

在上文中,簡述了經過AVCapturePhotoOutput、AVCapturePhotoSettings來實現代理,獲取當前攝像頭所捕捉到的photo數據,生成一張圖片。數組

視頻錄製過程大體也是如此,經過AVCaptureMovieFileOutput來獲取視頻數據,大體流程以下:bash

  1. 開啓錄製以前須要判斷當前是否處於錄製狀態,只有在非錄製狀態才能進入錄製狀態
/// 是否在錄製狀態
- (BOOL)isRecording {
    return self.movieOutput.isRecording;
}
複製代碼
  1. 經過AVCaptureConnection來獲取當前視頻捕捉的鏈接信息
    1. 調整視頻方向
    2. 判斷是否支持視頻穩定功能(非必要)
    3. 判讀是否支持平滑對焦(非必要)
    4. 爲視頻輸出配置輸出路徑
    5. 開始視頻recording
/// 開始錄製
- (void)startRecording {
    
    if (![self isRecording]) {
        //獲取當前視頻捕捉鏈接信息
        AVCaptureConnection *videoConnection = [self.movieOutput connectionWithMediaType:AVMediaTypeVideo];
        
        //調整方向
        if ([videoConnection isVideoOrientationSupported]) {
            videoConnection.videoOrientation = [self currentVideoOrientation];
        }
        
        //判斷是否支持視頻穩定功能(保證視頻質量)
        if ([videoConnection isVideoStabilizationSupported]) {
            videoConnection.preferredVideoStabilizationMode = YES;
        }
        
        //拿到活躍的攝像頭
        AVCaptureDevice *device = [self activeCamera];
        //判斷是否支持平滑對焦(當用戶移動設備時, 能自動且快速的對焦)
        if (device.isSmoothAutoFocusEnabled) {
            NSError *error;
            if ([device lockForConfiguration:&error]) {
                device.smoothAutoFocusEnabled = YES;
                [device unlockForConfiguration];
            } else {
                //失敗回調
                
            }
        }
        
        //獲取路徑
        self.outputURL = [self uniqueURL];

        //攝像頭的相關配置完成, 也獲取到路徑, 開始錄製(這裏錄製QuckTime視頻文件, 保存到相冊)
        [self.movieOutput startRecordingToOutputFileURL:self.outputURL recordingDelegate:self];
        
    }
}
複製代碼
  1. 中止視頻recording
/// 中止錄製
- (void)stopRecording {
    if ([self isRecording]) {
        [self.movieOutput stopRecording];
    }
}
///路徑轉換
- (NSURL *)uniqueURL {
    NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), @"output.mov"]];
    return url;
}
///獲取方向值
- (AVCaptureVideoOrientation)currentVideoOrientation {
    AVCaptureVideoOrientation result;
    
    UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
    switch (deviceOrientation) {
        case UIDeviceOrientationPortrait:
        case UIDeviceOrientationFaceUp:
        case UIDeviceOrientationFaceDown:
            result = AVCaptureVideoOrientationPortrait;
            break;
        case UIDeviceOrientationPortraitUpsideDown:
            //若是這裏設置成AVCaptureVideoOrientationPortraitUpsideDown,則視頻方向和拍攝時的方向是相反的。
            result = AVCaptureVideoOrientationPortrait;
            break;
        case UIDeviceOrientationLandscapeLeft:
            result = AVCaptureVideoOrientationLandscapeRight;
            break;
        case UIDeviceOrientationLandscapeRight:
            result = AVCaptureVideoOrientationLandscapeLeft;
            break;
        default:
            result = AVCaptureVideoOrientationPortrait;
            break;
    }
    return result;
}

複製代碼
  1. 保存影片至相冊
///經過代理來獲取視頻數據
#pragma mark - AVCaptureFileOutputRecordingDelegate
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
      fromConnections:(NSArray *)connections
                error:(NSError *)error {
    if (error) {
        //錯誤回調
        
    } else {
        //視頻寫入到相冊
        [self writeVideoToAssetsLibrary:[self.outputURL copy]];
    }
    self.outputURL = nil;
}
//寫入捕捉到的視頻
- (void)writeVideoToAssetsLibrary:(NSURL *)videoURL {
    
    __block PHObjectPlaceholder *assetPlaceholder = nil;
    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
        //保存進相冊
        PHAssetChangeRequest *changeRequest = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:videoURL];
        assetPlaceholder = changeRequest.placeholderForCreatedAsset;
        
    } completionHandler:^(BOOL success, NSError * _Nullable error) {
        NSLog(@"OK");
        //保存成功
        dispatch_async(dispatch_get_main_queue(), ^{
            
            //通知外部一個略縮圖
            [self generateThumbnailForVideoAtURL:videoURL];

        });
    
    }];
}
複製代碼
  1. 生成一個略縮圖通知外部
///經過視頻獲取視頻的第一幀圖片當作略縮圖
- (void)generateThumbnailForVideoAtURL:(NSURL *)videoURL {
    
    dispatch_async(self.videoQueue, ^{
       
        //拿到視頻信息
        AVAsset *asset = [AVAsset assetWithURL:videoURL];
        AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
        imageGenerator.maximumSize = CGSizeMake(100, 0);
        imageGenerator.appliesPreferredTrackTransform = YES;
        
        //經過視頻將第一幀圖片數據轉化爲CGImage
        CGImageRef imageRef = [imageGenerator copyCGImageAtTime:kCMTimeZero actualTime:NULL error:nil];
        UIImage *image = [UIImage imageWithCGImage:imageRef];
        
        //通知外部
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        [nc postNotificationName:ThumbnailCreatedNotification object:image];
        
    });
    
    
}

複製代碼

1.二、關於QuickTime

上文主要是經過AVCaptureMovieFileOutput將QuickTime影片捕捉到磁盤,這個類大多數核心功能繼承與超類AVCaptureFileOutput。它有不少實用的功能,例如:錄製到最長時限或錄製到特定文件大小爲止。session

一般當QuickTime影片準備發佈時,影片頭的元數據處於文件的開始位置。這樣可讓視頻播放器快速讀取頭包含信息,來肯定文件的內容、結構和其包含的多個樣本的位置。當錄製一個QuickTime影片時,直到全部的樣片都完成捕捉後才能建立信息頭。當錄製結束時,建立頭數據並將它附在文件結尾。app

將建立頭的過程放在全部影片樣本完成捕捉以後存在一個問題。 在移動設備中,好比錄製的時候接到電話或者程序崩潰等問題,影片頭就不能被正確寫入。會在磁盤生成一個不可讀的影片文件。AVCaptureMovieFileOutput提供一個核心功能就是分段捕捉QuickTime影片。async

2、AVFoundation的人臉識別

人臉識別其實是很是複雜的一個功能,要想本身徹底實現人臉識別是很是困難的。蘋果爲咱們作了不少人臉識別的功能,例如CoreImage、AVFoundation,都是有人臉識別的功能的。還有Vision face++ 等。這裏就簡單介紹一下AVFoundation中的人臉識別。ide

在拍攝視頻中,咱們經過AVFoundation的人臉識別,在屏幕界面上用一個紅色矩形來標識識別到的人臉。post

2.一、人臉識別流程

  1. 使用AVCaptureMetadataOutput來創建輸出
    1. 添加進session
    2. 設置獲取數據類型
    3. 在主線程中執行任務
- (BOOL)setupSessionOutputs:(NSError **)error {
    //配置輸入信息
    self.metadataOutput = [[AVCaptureMetadataOutput alloc] init];
    //對session添加輸出
    if ([self.captureSession canAddOutput:self.metadataOutput]) {
        [self.captureSession addOutput:self.metadataOutput];
        //從輸出數據中設置只獲取人臉數據(能夠是人臉、二維碼、一維碼....)
        NSArray *metadataObjectType = @[AVMetadataObjectTypeFace];
        self.metadataOutput.metadataObjectTypes = metadataObjectType;
        
        //由於人臉檢測使用了硬件加速器GPU, 因此它的任務須要在主線程中執行
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
        //設置metadataOutput代理方法, 檢測視頻中一幀一幀數據裏是否包含人臉數據. 若是包含則調用回調方法
        [self.metadataOutput setMetadataObjectsDelegate:self queue:mainQueue];
        return YES;
    } else {
        //錯誤回調
    }
    return NO;
}

複製代碼
  1. 實現相關代理方法,將捕捉到的人臉數據傳遞給layer層
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection {

    //metadataObjects包含了捕獲到的人臉數據(人臉數據會重複, 會一直捕獲人臉數據)
    for (AVMetadataFaceObject *face in metadataObjects) {
        NSLog(@"Face ID:%li",(long)face.faceID);
    }
    //將人臉數據經過代理髮送給外部的layer層
    [self.faceDetectionDelegate didDetectFaces:metadataObjects];
}

複製代碼
  1. 配置相關顯示的圖層。在layer層的視圖中配置圖層,咱們在人臉四周添加一個矩形是在這個AVCaptureVideoPreviewLayer上進行一個個添加矩形。(由於人臉在識別過程當中會出現旋轉抖動等,須要進行一些3D轉換等操做,後續也會出現此類操做,不在此篇做過多講解)
- (void)setupView {

    //用來記錄人臉圖層
    self.faceLayers = [NSMutableDictionary dictionary];
    //圖層的填充方式: 設置videoGravity 使用AVLayerVideoGravityResizeAspectFill 鋪滿整個預覽層的邊界範圍
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    //在previewLayer上添加一個透明的圖層
    self.overlayLayer = [CALayer layer];
    self.overlayLayer.frame = self.bounds;
    //假設你的圖層上的圖形會發生3D變換, 設置投影方式
    self.overlayLayer.sublayerTransform = CATransform3DMakePerspective(1000);
    [self.previewLayer addSublayer:self.overlayLayer];
}
static CATransform3D CATransform3DMakePerspective(CGFloat eyePosition) {
    //CATransform3D 圖層的旋轉,縮放,偏移,歪斜和應用的透
    //CATransform3DIdentity是單位矩陣,該矩陣沒有縮放,旋轉,歪斜,透視。該矩陣應用到圖層上,就是設置默認值。
    CATransform3D  transform = CATransform3DIdentity;
    //透視效果(就是近大遠小),是經過設置m34 m34 = -1.0/D 默認是0.D越小透視效果越明顯
    //D:eyePosition 觀察者到投射面的距離
    transform.m34 = -1.0/eyePosition;
    
    return transform;
}

複製代碼
  1. 處理經過代理傳遞過來的人臉數據
    1. 將人臉在攝像頭中的座標轉化爲屏幕座標
    2. 定義一個數組,保存全部的人臉數據,用於存放待從屏幕上刪除的人臉數據
    3. 遍歷人臉數據
      1. 經過對比屏幕上的layer(框框)數量來與傳遞過來的人臉進行對比,判斷是否須要移除layer(框框)
      2. 根據人臉數據的ID來從屏幕上的layer(框框)中查找是否已經存在,不存在則須要生成一個layer(框框),並更新屏幕的layer(框框)數組。
      3. 根據傳遞過來的人臉數據來設置layer(框框)的位置,注意:當最後一我的臉離開屏幕,此時代理方法不會調用,會致使最後一個layer(框框)仍停留在屏幕上,因此須要處理一下人臉將要離開屏幕就對其進行移除處理。
      4. 在捕捉過程當中,人臉會左右先後擺動(即z、y軸變化),來作不一樣的處理
    4. 遍歷一下待刪除數組,將之與傳遞過來的人臉數據進行對比,刪除多餘的人臉數據

注意:此處省略了一些3D轉換的方法ui

- (void)didDetectFaces:(NSArray *)faces {
    //人臉數據位置信息(攝像頭座標系)轉換爲屏幕座標系
    NSArray *transfromedFaces = [self transformedFacesFromFaces:faces];
    
    //人臉消失, 刪除圖層
    
    //須要刪除的人臉數據列表
    NSMutableArray *lostFaces = [self.faceLayers.allValues mutableCopy];

    //遍歷每一個人臉數據
    for (AVMetadataFaceObject *face in transfromedFaces) {
        
        //face ID
        NSNumber *faceID = @(face.faceID);
        //face ID存在即不須要刪除(從刪除列表中移除)
        [lostFaces removeObject:faceID];

        //假若有新的人臉加入
        CALayer *layer = self.faceLayers[faceID];
        if (!layer) {
            NSLog(@"新增人臉");
            layer = [self makeFaceLayer];
            [self.overlayLayer addSublayer:layer];
            
            //更新字典
            self.faceLayers[faceID] = layer;
        }
        
        //根據人臉的bounds設置layer的frame
        layer.frame = face.bounds;
        CGSize size = self.bounds.size;
        //當人臉特別靠近屏幕邊緣, 直接看成沒法識別此人臉(由於人臉離開屏幕不會走此代理方法, 須要提早作移除)
        if (face.bounds.origin.x < 3 ||
            face.bounds.origin.x > size.width - layer.frame.size.width - 3 ||
            face.bounds.origin.y < 3 ||
            face.bounds.origin.y > size.height - layer.frame.size.height - 3 ) {
            [layer removeFromSuperlayer];
            [self.faceLayers removeObjectForKey:faceID];
        }
        
        //設置3D屬性(人臉是3D的, 須要根據人臉的3D變化作不一樣的變化處理)
        layer.transform = CATransform3DIdentity;
        //人臉z軸變化
        if (face.hasRollAngle) {
            CATransform3D t = [self transformForRollAngle:face.rollAngle];
            //矩陣相乘
            layer.transform = CATransform3DConcat(layer.transform, t);
        }
        //人臉y軸變化
        if (face.hasYawAngle) {
            CATransform3D t = [self transformForYawAngle:face.hasYawAngle];
            //矩陣相乘
            layer.transform = CATransform3DConcat(layer.transform, t);
        }
    }
    //處理已經從鏡頭消失的人臉(人臉消失,圖層並無消失)
    for (NSNumber *faceID in lostFaces) {
        CALayer *layer = self.faceLayers[faceID];
        [self.faceLayers removeObjectForKey:faceID];
        [layer removeFromSuperlayer];
    }
}

複製代碼

2.一、其餘類型數據的識別

有的同窗在設置AVMetadataObjectTypeFace的可能會發現,還有會有一些其餘的類型,例如AVMetadataObjectTypeQRCode等,就是從攝像頭中捕獲二維碼數據,它的流程與人臉識別極度類似,甚至要更爲簡單一些,由於二維碼並不像人臉同樣須要作一些3D的轉換等操做,因此此處再也不示例捕捉二維碼。url

相關文章
相關標籤/搜索