欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

AVFoundation AVCaptureSession媒體捕捉

 更新時(shí)間:2022年10月26日 17:15:57   作者:小猴子Monkey  
這篇文章主要為大家介紹了ios開發(fā)AVFoundation AVCaptureSession媒體捕捉詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

AVFoundation 是Apple iOS和OS X系統(tǒng)中用于處理基于時(shí)間的媒體數(shù)據(jù)的高級框架,通過開發(fā)所需的工具提供了強(qiáng)大的功能集,讓開發(fā)者能夠基于蘋果平臺(tái)創(chuàng)建當(dāng)下最先進(jìn)的媒體應(yīng)用程序,其針對64位處理器設(shè)計(jì),充分利用了多核硬件優(yōu)勢,會(huì)自動(dòng)提供硬件加速操作,確保大部分設(shè)備能以最佳性能運(yùn)行,是iOS開發(fā)接觸音視頻開發(fā)必學(xué)的框架之一

參與掘金日新計(jì)劃,持續(xù)記錄AVFoundation學(xué)習(xí),Demo學(xué)習(xí)地址,里面封裝了一些工具類,可以直接使用,這篇文章主要講述AVFoundation中的AVCaptureSession等類實(shí)現(xiàn)媒體捕捉功能,其他類的相關(guān)用法可查看我的其他文章。

捕捉媒體

媒體捕捉是AVFoundation的核心功能之一,也是開發(fā)音視頻App必不可少的功能。捕捉用到的類如圖所示

  • AVCaptureSession是AVFoundation捕捉棧的核心類,捕捉會(huì)話用于連接輸入和輸出資源,管理從物理設(shè)備得到的輸入流,例如從攝像頭得到的視頻從麥克風(fēng)得到的音頻,以不同的方式輸出給一個(gè)或多個(gè)輸出,可以動(dòng)態(tài)配置輸入和輸出線路,讓開發(fā)者能夠在會(huì)話中按需重新配置捕捉環(huán)境。
  • AVCaptureDevice為攝像頭麥克風(fēng)等物理設(shè)備定義了一個(gè)接口,在iOS10以后,使用AVCaptureDeviceDiscoverySession獲取設(shè)備。
  • AVCaptureDevice包裝成AVCaptureDeviceInput才能添加到捕捉回話中。
  • AVFoundation定義了AVCaptureOutput的許多擴(kuò)展類,AVCaptureOutput是一個(gè)抽象基類,用于從捕捉回話中得到數(shù)據(jù),框架定義了這個(gè)抽象類的高級擴(kuò)展類,常用的有AVCaptureStillImageOutput靜態(tài)圖片輸出、AVCaptureMovieFileOutput視頻文件輸出、AVCaptureVideoDataOutput視頻流數(shù)據(jù)輸出、AVCaptureAudioDataOutput音頻流數(shù)據(jù)輸出、AVCaptureMetadataOutput元數(shù)據(jù)輸出。注意,不能同時(shí)配置AVCaptureVideoDataOutputAVCaptureMovieFileOutput,二者無法同時(shí)啟用。
  • AVCaptureVideoPreviewLayer是CoreAnimation框架中CALayer的一個(gè)子類,對捕捉視頻數(shù)據(jù)實(shí)時(shí)預(yù)覽。當(dāng)然也可以使用GLKView、UIImageView預(yù)覽實(shí)時(shí)視頻流的Buffer。

具體代碼可以看Demo中的CQCaptureManager類對捕捉工具的封裝

1.創(chuàng)建會(huì)話

創(chuàng)建會(huì)話并配置分辨率

  • 配置分辨率注意要判斷下能否支持,例如老機(jī)型前置攝像頭配置4k是不支持的。
  • 不同分辨率的縮放倍數(shù)也是不同的
self.captureSession = [[AVCaptureSession alloc] init];
- (void)configSessionPreset:(AVCaptureSessionPreset)sessionPreset {
    [self.captureSession beginConfiguration];
    if ([self.captureSession canSetSessionPreset:sessionPreset])  {
        self.captureSession.sessionPreset = sessionPreset;
    } else {
        self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;
    }
    [self.captureSession commitConfiguration];
    self.isConfigSessionPreset = YES;
}

2.配置視頻輸入

/// 配置視頻輸入
- (BOOL)configVideoInput:(NSError * _Nullable *)error {
    // 添加視頻捕捉設(shè)備
    // 拿到默認(rèn)視頻捕捉設(shè)備 iOS默認(rèn)后置攝像頭
//    AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    AVCaptureDevice *videoDevice = [self getCameraWithPosition:AVCaptureDevicePositionBack];
    // 將捕捉設(shè)備轉(zhuǎn)化為AVCaptureDeviceInput
    // 注意:會(huì)話不能直接使用AVCaptureDevice,必須將AVCaptureDevice封裝成AVCaptureDeviceInput對象
    AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
    // 將捕捉設(shè)備添加給會(huì)話
    // 使用前判斷videoInput是否有效以及能否添加,因?yàn)閿z像頭是一個(gè)公共設(shè)備,不屬于任何App,有可能別的App在使用,添加前應(yīng)該先進(jìn)行判斷是否可以添加
    if (videoInput && [self.captureSession canAddInput:videoInput]) {
        // 將videoInput 添加到 captureSession中
        [self.captureSession beginConfiguration];
        [self.captureSession addInput:videoInput];
        [self.captureSession commitConfiguration];
        self.videoDeviceInput = videoInput;
        return YES;
    }else {
        return NO;
    }
}
/// 移除視頻輸入設(shè)備
- (void)removeVideoDeviceInput {
    if (self.videoDeviceInput) [self.captureSession removeInput:self.videoDeviceInput];
    self.videoDeviceInput = nil;
}
  • 獲取攝像頭,iOS10之后使用AVCaptureDeviceDiscoverySession獲取
  • 長焦超廣或者雙攝三攝必須使用AVCaptureDeviceDiscoverySession獲取,[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]無法獲取
/// 根據(jù)position拿到攝像頭
- (AVCaptureDevice *)getCameraWithPosition:(AVCaptureDevicePosition)position {
    /**
     AVCaptureDeviceTypeBuiltInWideAngleCamera 廣角(默認(rèn)設(shè)備,28mm左右焦段)
     AVCaptureDeviceTypeBuiltInTelephotoCamera 長焦(默認(rèn)設(shè)備的2x或3x,只能使用AVCaptureDeviceDiscoverySession獲取)
     AVCaptureDeviceTypeBuiltInUltraWideCamera 超廣角(默認(rèn)設(shè)備的0.5x,只能使用AVCaptureDeviceDiscoverySession獲取)
     AVCaptureDeviceTypeBuiltInDualCamera (一個(gè)廣角一個(gè)長焦(iPhone7P,iPhoneX),可以自動(dòng)切換攝像頭,只能使用AVCaptureDeviceDiscoverySession獲取)
     AVCaptureDeviceTypeBuiltInDualWideCamera (一個(gè)超廣一個(gè)廣角(iPhone12 iPhone13),可以自動(dòng)切換攝像頭,只能使用AVCaptureDeviceDiscoverySession獲取)
     AVCaptureDeviceTypeBuiltInTripleCamera (超廣,廣角,長焦三攝像頭,iPhone11ProMax iPhone12ProMax iPhone13ProMax,可以自動(dòng)切換攝像頭,只能使用AVCaptureDeviceDiscoverySession獲取)
     AVCaptureDeviceTypeBuiltInTrueDepthCamera (紅外和攝像頭, iPhone12ProMax iPhone13ProMax )
     */
    NSArray *deviceTypes;
    if (position == AVCaptureDevicePositionBack) {
        deviceTypes = @[AVCaptureDeviceTypeBuiltInDualCamera,
                        AVCaptureDeviceTypeBuiltInDualWideCamera,
                        AVCaptureDeviceTypeBuiltInTripleCamera, ];
    } else {
        deviceTypes = @[AVCaptureDeviceTypeBuiltInWideAngleCamera];
    }
    AVCaptureDeviceDiscoverySession *deviceSession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:deviceTypes mediaType:AVMediaTypeVideo position:position];
    if (deviceSession.devices.count) return deviceSession.devices.firstObject;
    if (position == AVCaptureDevicePositionBack) {
        // 非多攝手機(jī)
        deviceTypes = @[AVCaptureDeviceTypeBuiltInWideAngleCamera];
        AVCaptureDeviceDiscoverySession *deviceSession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:deviceTypes mediaType:AVMediaTypeVideo position:position];
        if (deviceSession.devices.count) return deviceSession.devices.firstObject;
    }
    return nil;
}

3.配置音頻輸入

/// 配置音頻輸入
- (BOOL)configAudioInput:(NSError * _Nullable *)error {
    // 添加音頻捕捉設(shè)備 ,如果只是拍攝靜態(tài)圖片,可以不用設(shè)置
    // 選擇默認(rèn)音頻捕捉設(shè)備 即返回一個(gè)內(nèi)置麥克風(fēng)
    AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    self.audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:error];
    if (self.audioDeviceInput && [self.captureSession canAddInput:self.audioDeviceInput]) {
        [self.captureSession beginConfiguration];
        [self.captureSession addInput:self.audioDeviceInput];
        [self.captureSession commitConfiguration];
        return YES;
    }else {
        return NO;
    }
}
/// 移除音頻輸入設(shè)備
- (void)removeAudioDeviceInput {
    if (self.audioDeviceInput) [self.captureSession removeInput:self.audioDeviceInput];
}

5.配置輸出

#pragma mark - Func 靜態(tài)圖片輸出配置
/// 配置靜態(tài)圖片輸出
- (void)configStillImageOutput {
    // AVCaptureStillImageOutput 從攝像頭捕捉靜態(tài)圖片
    self.stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
    // 配置字典:希望捕捉到JPEG格式的圖片
    self.stillImageOutput.outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
    // 輸出連接 判斷是否可用,可用則添加到輸出連接中去
    [self.captureSession beginConfiguration];
    if ([self.captureSession canAddOutput:self.stillImageOutput]) {
        [self.captureSession addOutput:self.stillImageOutput];
    }
    [self.captureSession commitConfiguration];
}
/// 移除靜態(tài)圖片輸出
- (void)removeStillImageOutput {
    if (self.stillImageOutput) [self.captureSession removeOutput:self.stillImageOutput];
}
#pragma mark - Func 電影文件輸出配置
/// 配置電影文件輸出
- (void)configMovieFileOutput {
    // AVCaptureMovieFileOutput,將QuickTime視頻錄制到文件系統(tǒng)
    self.movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
    [self.captureSession beginConfiguration];
    if ([self.captureSession canAddOutput:self.movieFileOutput]) {
        [self.captureSession addOutput:self.movieFileOutput];
    }
    [self.captureSession commitConfiguration];
}
/// 移除電影文件輸出
- (void)removeMovieFileOutput {
    if (self.movieFileOutput) [self.captureSession removeOutput:self.movieFileOutput];
}

6.開始會(huì)話\結(jié)束會(huì)話

// 異步開始會(huì)話
- (void)startSessionAsync {
    // 檢查是否處于運(yùn)行狀態(tài)
    if (![self.captureSession isRunning]) {
        // 使用同步調(diào)用會(huì)損耗一定的時(shí)間,則用異步的方式處理
        dispatch_async(self.captureVideoQueue, ^{
            [self.captureSession startRunning];
        });
    }
}
// 異步停止會(huì)話
- (void)stopSessionAsync {
    // 檢查是否處于運(yùn)行狀態(tài)
    if ([self.captureSession isRunning]) {
        dispatch_async(self.captureVideoQueue, ^{
            [self.captureSession stopRunning];
        });
    }
}

7.捕捉靜態(tài)圖片

#pragma mark - 靜態(tài)圖片捕捉
#pragma mark Public Func 靜態(tài)圖片捕捉
// 捕捉靜態(tài)圖片
- (void)captureStillImage {
    if (!self.isConfigSessionPreset) [self configSessionPreset:AVCaptureSessionPresetMedium];
    if (!self.videoDeviceInput) {
        NSError *configError;
        BOOL configResult = [self configVideoInput:&configError];
        if (!configResult) return;
    }
    if (!self.stillImageOutput) [self configStillImageOutput];
    [self startSessionSync];
    // 獲取圖片輸出連接
    AVCaptureConnection *connection = [self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
    // 即使程序只支持縱向,但是如果用戶橫向拍照時(shí),需要調(diào)整結(jié)果照片的方向
    // 判斷是否支持設(shè)置視頻方向, 支持則根據(jù)設(shè)備方向設(shè)置輸出方向值
    if (connection.isVideoOrientationSupported) {
        connection.videoOrientation = [self getCurrentVideoOrientation];
    }
    [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:^(CMSampleBufferRef  _Nullable imageDataSampleBuffer, NSError * _Nullable error) {        if (imageDataSampleBuffer != NULL) {            dispatch_async(dispatch_get_main_queue(), ^{                if (self.delegate && [self.delegate respondsToSelector:@selector(mediaCaptureImageFileSuccess)]) {
                    [self.delegate mediaCaptureImageFileSuccess];
                }
            });
            // CMSampleBufferRef轉(zhuǎn)UIImage 并寫入相冊
            NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
            UIImage *image = [[UIImage alloc] initWithData:imageData];
            [self writeImageToAssetsLibrary:image];
        } else {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (self.delegate && [self.delegate respondsToSelector:@selector(mediaCaptureImageFailedWithError:)]) {
                    [self.delegate mediaCaptureImageFailedWithError:error];
                }
            });
            NSLog(@"NULL sampleBuffer:%@",[error localizedDescription]);
        }
    }];
}
#pragma mark Private Func 靜態(tài)圖片捕捉
/**
 Assets Library 框架
 用來讓開發(fā)者通過代碼方式訪問iOS photo
 注意:會(huì)訪問到相冊,需要修改plist 權(quán)限。否則會(huì)導(dǎo)致項(xiàng)目崩潰
 */
/// 將UIImage寫入到用戶相冊
- (void)writeImageToAssetsLibrary:(UIImage *)image {
    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
    // 參數(shù)1 圖片, 參數(shù)2 方向, 參數(shù)3 回調(diào)
    [library writeImageToSavedPhotosAlbum:image.CGImage orientation:(NSUInteger)image.imageOrientation completionBlock:^(NSURL *assetURL, NSError *error) {        if (!error) {            dispatch_async(dispatch_get_main_queue(), ^{                if (self.delegate && [self.delegate respondsToSelector:@selector(assetLibraryWriteImageSuccessWithImage:)]) {
                    [self.delegate assetLibraryWriteImageSuccessWithImage:image];
                }
            });
        } else {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (self.delegate && [self.delegate respondsToSelector:@selector(assetLibraryWriteImageFailedWithError:)]) {
                    [self.delegate assetLibraryWriteImageFailedWithError:error];
                }
            });
        }
    }];
}

8.捕捉視頻文件

#pragma mark - 電影文件捕捉
#pragma mark Public Func 電影文件捕捉
// 開始錄制電影文件
- (void)startRecordingMovieFile {
    if (!self.isConfigSessionPreset) [self configSessionPreset:AVCaptureSessionPresetMedium];
    if (!self.videoDeviceInput) {
        NSError *configError;
        BOOL configResult = [self configVideoInput:&configError];
        if (!configResult) return;
    }
    if (!self.movieFileOutput) [self configMovieFileOutput];
    [self startSessionSync];
    if ([self isRecordingMovieFile]) return;
    AVCaptureConnection *videoConnection = [self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
    // 設(shè)置輸出方向
    // 即使程序只支持縱向,但是如果用戶橫向拍照時(shí),需要調(diào)整結(jié)果照片的方向
    // 判斷是否支持設(shè)置視頻方向, 支持則根據(jù)設(shè)備方向設(shè)置輸出方向值
    if (videoConnection.isVideoOrientationSupported) {
        videoConnection.videoOrientation = [self getCurrentVideoOrientation];
    }
    // 設(shè)置視頻幀穩(wěn)定
    // 判斷是否支持視頻穩(wěn)定 可以顯著提高視頻的質(zhì)量。只會(huì)在錄制視頻文件涉及
//    if (videoConnection.isVideoStabilizationSupported) {
//        videoConnection.enablesVideoStabilizationWhenAvailable = YES;
//    }
    videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
    // 設(shè)置對焦
    AVCaptureDevice *device = [self getActiveCamera];
    // 攝像頭可以進(jìn)行平滑對焦模式操作。即減慢攝像頭鏡頭對焦速度。當(dāng)用戶移動(dòng)拍攝時(shí)攝像頭會(huì)嘗試快速自動(dòng)對焦。
    if (device.isSmoothAutoFocusEnabled) {
        NSError *error;
        if ([device lockForConfiguration:&error]) {
            device.smoothAutoFocusEnabled = YES;
            [device unlockForConfiguration];
        } else {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (self.delegate && [self.delegate respondsToSelector:@selector(deviceConfigurationFailedWithError:)]) {
                    [self.delegate deviceConfigurationFailedWithError:error];
                }
            });
        }
    }
    self.movieFileOutputURL = [self getVideoTempPathURL];
    // 開始錄制 參數(shù)1:錄制保存路徑  參數(shù)2:代理
    [self.movieFileOutput startRecordingToOutputFileURL:self.movieFileOutputURL recordingDelegate:self];
}
// 停止錄制電影文件
- (void)stopRecordingMovieFile {
    if ([self isRecordingMovieFile]) {
        [self.movieFileOutput stopRecording];
    }
}
// 是否在錄制電影文件
- (BOOL)isRecordingMovieFile {
    return self.movieFileOutput.isRecording;
}
// 錄制電影文件的時(shí)間
- (CMTime)movieFileRecordedDuration {
    return self.movieFileOutput.recordedDuration;
}
#pragma mark AVCaptureFileOutputRecordingDelegate
/// 捕捉電影文件成功的回調(diào)
- (void)captureOutput:(AVCaptureFileOutput *)output didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray<AVCaptureConnection *> *)connections error:(NSError *)error {
    if (error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (self.delegate && [self.delegate respondsToSelector:@selector(mediaCaptureMovieFileFailedWithError:)]) {
                [self.delegate mediaCaptureMovieFileFailedWithError:error];
            }
        });
    } else {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (self.delegate && [self.delegate respondsToSelector:@selector(mediaCaptureMovieFileSuccess)]) {
                [self.delegate mediaCaptureMovieFileSuccess];
            }
        });
        // copy一個(gè)副本再置為nil
        // 將文件寫入相冊
        [self writeVideoToAssetsLibrary:self.movieFileOutputURL.copy];
        self.movieFileOutputURL = nil;
    }
}
#pragma mark Private Func 電影文件捕捉
/// 創(chuàng)建視頻文件臨時(shí)路徑URL
- (NSURL *)getVideoTempPathURL {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *tempPath = [fileManager temporaryDirectoryWithTemplateString:@"video.XXXXXX"];
    if (tempPath) {
        NSString *filePath = [tempPath stringByAppendingPathComponent:@"temp_video.mov"];
        return [NSURL fileURLWithPath:filePath];
    }
    return nil;
}
/// 將視頻文件寫入到用戶相冊
- (void)writeVideoToAssetsLibrary:(NSURL *)videoURL {
    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
    // 和圖片不同,視頻的寫入更耗時(shí),所以寫入之前應(yīng)該判斷是否能寫入
    if (![library videoAtPathIsCompatibleWithSavedPhotosAlbum:videoURL]) return;
    [library writeVideoAtPathToSavedPhotosAlbum:videoURL completionBlock:^(NSURL *assetURL, NSError *error) {
        if (error) {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (self.delegate && [self.delegate respondsToSelector:@selector(assetLibraryWriteMovieFileFailedWithError:)]) {
                    [self.delegate assetLibraryWriteMovieFileFailedWithError:error];
                }
            });
        } else {
            // 寫入成功 回調(diào)封面圖
            [self getVideoCoverImageWithVideoURL:videoURL callBlock:^(UIImage *coverImage) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (self.delegate && [self.delegate respondsToSelector:@selector(assetLibraryWriteMovieFileSuccessWithCoverImage:)]) {
                        [self.delegate assetLibraryWriteMovieFileSuccessWithCoverImage:coverImage];
                    }
                });
            }];
        }
    }];
}
/// 獲取視頻文件封面圖
- (void)getVideoCoverImageWithVideoURL:(NSURL *)videoURL callBlock:(void(^)(UIImage *))callBlock {
    dispatch_async(self.captureVideoQueue, ^{
        AVAsset *asset = [AVAsset assetWithURL:videoURL];
        AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
        // 設(shè)置maximumSize 寬為100,高為0 根據(jù)視頻的寬高比來計(jì)算圖片的高度
        imageGenerator.maximumSize = CGSizeMake(100.0f, 0.0f);
        // 捕捉視頻縮略圖會(huì)考慮視頻的變化(如視頻的方向變化),如果不設(shè)置,縮略圖的方向可能出錯(cuò)
        imageGenerator.appliesPreferredTrackTransform = YES;
        CGImageRef imageRef = [imageGenerator copyCGImageAtTime:kCMTimeZero actualTime:NULL error:nil];
        UIImage *image = [UIImage imageWithCGImage:imageRef];
        CGImageRelease(imageRef);
        dispatch_async(dispatch_get_main_queue(), ^{
            !callBlock ?: callBlock(image);
        });
    });
}

9.預(yù)覽視頻

previewView.session = captureManager.captureSession

以上就是AVFoundation AVCaptureSession媒體捕捉的詳細(xì)內(nèi)容,更多關(guān)于AVFoundation AVCaptureSession的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論