網站首頁 編程語言 正文
正文
AVFoundation 是Apple iOS和OS X系統中用于處理基于時間的媒體數據的高級框架,通過開發所需的工具提供了強大的功能集,讓開發者能夠基于蘋果平臺創建當下最先進的媒體應用程序,其針對64位處理器設計,充分利用了多核硬件優勢,會自動提供硬件加速操作,確保大部分設備能以最佳性能運行,是iOS開發接觸音視頻開發必學的框架之一。
參與掘金日新計劃,持續記錄AVFoundation學習,Demo學習地址,里面封裝了一些工具類,可以直接使用,這篇文章主要講述AVFoundation中的AVCaptureSession等類實現媒體捕捉功能,其他類的相關用法可查看我的其他文章。
捕捉媒體
媒體捕捉是AVFoundation的核心功能之一,也是開發音視頻App必不可少的功能。捕捉用到的類如圖所示
- AVCaptureSession是AVFoundation捕捉棧的核心類,捕捉會話用于連接輸入和輸出資源,管理從物理設備得到的輸入流,例如從攝像頭得到的視頻從麥克風得到的音頻,以不同的方式輸出給一個或多個輸出,可以動態配置輸入和輸出線路,讓開發者能夠在會話中按需重新配置捕捉環境。
- AVCaptureDevice為攝像頭麥克風等物理設備定義了一個接口,在iOS10以后,使用AVCaptureDeviceDiscoverySession獲取設備。
- 將AVCaptureDevice包裝成AVCaptureDeviceInput才能添加到捕捉回話中。
- AVFoundation定義了AVCaptureOutput的許多擴展類,AVCaptureOutput是一個抽象基類,用于從捕捉回話中得到數據,框架定義了這個抽象類的高級擴展類,常用的有AVCaptureStillImageOutput靜態圖片輸出、AVCaptureMovieFileOutput視頻文件輸出、AVCaptureVideoDataOutput視頻流數據輸出、AVCaptureAudioDataOutput音頻流數據輸出、AVCaptureMetadataOutput元數據輸出。注意,不能同時配置AVCaptureVideoDataOutput和AVCaptureMovieFileOutput,二者無法同時啟用。
- AVCaptureVideoPreviewLayer是CoreAnimation框架中CALayer的一個子類,對捕捉視頻數據實時預覽。當然也可以使用GLKView、UIImageView預覽實時視頻流的Buffer。
具體代碼可以看Demo中的CQCaptureManager類對捕捉工具的封裝
1.創建會話
創建會話并配置分辨率
- 配置分辨率注意要判斷下能否支持,例如老機型前置攝像頭配置4k是不支持的。
- 不同分辨率的縮放倍數也是不同的
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 {
// 添加視頻捕捉設備
// 拿到默認視頻捕捉設備 iOS默認后置攝像頭
// AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDevice *videoDevice = [self getCameraWithPosition:AVCaptureDevicePositionBack];
// 將捕捉設備轉化為AVCaptureDeviceInput
// 注意:會話不能直接使用AVCaptureDevice,必須將AVCaptureDevice封裝成AVCaptureDeviceInput對象
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
// 將捕捉設備添加給會話
// 使用前判斷videoInput是否有效以及能否添加,因為攝像頭是一個公共設備,不屬于任何App,有可能別的App在使用,添加前應該先進行判斷是否可以添加
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;
}
}
/// 移除視頻輸入設備
- (void)removeVideoDeviceInput {
if (self.videoDeviceInput) [self.captureSession removeInput:self.videoDeviceInput];
self.videoDeviceInput = nil;
}
- 獲取攝像頭,iOS10之后使用AVCaptureDeviceDiscoverySession獲取
- 長焦超廣或者雙攝三攝必須使用AVCaptureDeviceDiscoverySession獲取,[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]無法獲取
/// 根據position拿到攝像頭
- (AVCaptureDevice *)getCameraWithPosition:(AVCaptureDevicePosition)position {
/**
AVCaptureDeviceTypeBuiltInWideAngleCamera 廣角(默認設備,28mm左右焦段)
AVCaptureDeviceTypeBuiltInTelephotoCamera 長焦(默認設備的2x或3x,只能使用AVCaptureDeviceDiscoverySession獲取)
AVCaptureDeviceTypeBuiltInUltraWideCamera 超廣角(默認設備的0.5x,只能使用AVCaptureDeviceDiscoverySession獲取)
AVCaptureDeviceTypeBuiltInDualCamera (一個廣角一個長焦(iPhone7P,iPhoneX),可以自動切換攝像頭,只能使用AVCaptureDeviceDiscoverySession獲取)
AVCaptureDeviceTypeBuiltInDualWideCamera (一個超廣一個廣角(iPhone12 iPhone13),可以自動切換攝像頭,只能使用AVCaptureDeviceDiscoverySession獲取)
AVCaptureDeviceTypeBuiltInTripleCamera (超廣,廣角,長焦三攝像頭,iPhone11ProMax iPhone12ProMax iPhone13ProMax,可以自動切換攝像頭,只能使用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) {
// 非多攝手機
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 {
// 添加音頻捕捉設備 ,如果只是拍攝靜態圖片,可以不用設置
// 選擇默認音頻捕捉設備 即返回一個內置麥克風
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;
}
}
/// 移除音頻輸入設備
- (void)removeAudioDeviceInput {
if (self.audioDeviceInput) [self.captureSession removeInput:self.audioDeviceInput];
}
5.配置輸出
#pragma mark - Func 靜態圖片輸出配置
/// 配置靜態圖片輸出
- (void)configStillImageOutput {
// AVCaptureStillImageOutput 從攝像頭捕捉靜態圖片
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];
}
/// 移除靜態圖片輸出
- (void)removeStillImageOutput {
if (self.stillImageOutput) [self.captureSession removeOutput:self.stillImageOutput];
}
#pragma mark - Func 電影文件輸出配置
/// 配置電影文件輸出
- (void)configMovieFileOutput {
// AVCaptureMovieFileOutput,將QuickTime視頻錄制到文件系統
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.開始會話\結束會話
// 異步開始會話
- (void)startSessionAsync {
// 檢查是否處于運行狀態
if (![self.captureSession isRunning]) {
// 使用同步調用會損耗一定的時間,則用異步的方式處理
dispatch_async(self.captureVideoQueue, ^{
[self.captureSession startRunning];
});
}
}
// 異步停止會話
- (void)stopSessionAsync {
// 檢查是否處于運行狀態
if ([self.captureSession isRunning]) {
dispatch_async(self.captureVideoQueue, ^{
[self.captureSession stopRunning];
});
}
}
7.捕捉靜態圖片
#pragma mark - 靜態圖片捕捉
#pragma mark Public Func 靜態圖片捕捉
// 捕捉靜態圖片
- (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];
// 即使程序只支持縱向,但是如果用戶橫向拍照時,需要調整結果照片的方向
// 判斷是否支持設置視頻方向, 支持則根據設備方向設置輸出方向值
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轉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 靜態圖片捕捉
/**
Assets Library 框架
用來讓開發者通過代碼方式訪問iOS photo
注意:會訪問到相冊,需要修改plist 權限。否則會導致項目崩潰
*/
/// 將UIImage寫入到用戶相冊
- (void)writeImageToAssetsLibrary:(UIImage *)image {
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
// 參數1 圖片, 參數2 方向, 參數3 回調
[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];
// 設置輸出方向
// 即使程序只支持縱向,但是如果用戶橫向拍照時,需要調整結果照片的方向
// 判斷是否支持設置視頻方向, 支持則根據設備方向設置輸出方向值
if (videoConnection.isVideoOrientationSupported) {
videoConnection.videoOrientation = [self getCurrentVideoOrientation];
}
// 設置視頻幀穩定
// 判斷是否支持視頻穩定 可以顯著提高視頻的質量。只會在錄制視頻文件涉及
// if (videoConnection.isVideoStabilizationSupported) {
// videoConnection.enablesVideoStabilizationWhenAvailable = YES;
// }
videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
// 設置對焦
AVCaptureDevice *device = [self getActiveCamera];
// 攝像頭可以進行平滑對焦模式操作。即減慢攝像頭鏡頭對焦速度。當用戶移動拍攝時攝像頭會嘗試快速自動對焦。
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];
// 開始錄制 參數1:錄制保存路徑 參數2:代理
[self.movieFileOutput startRecordingToOutputFileURL:self.movieFileOutputURL recordingDelegate:self];
}
// 停止錄制電影文件
- (void)stopRecordingMovieFile {
if ([self isRecordingMovieFile]) {
[self.movieFileOutput stopRecording];
}
}
// 是否在錄制電影文件
- (BOOL)isRecordingMovieFile {
return self.movieFileOutput.isRecording;
}
// 錄制電影文件的時間
- (CMTime)movieFileRecordedDuration {
return self.movieFileOutput.recordedDuration;
}
#pragma mark AVCaptureFileOutputRecordingDelegate
/// 捕捉電影文件成功的回調
- (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一個副本再置為nil
// 將文件寫入相冊
[self writeVideoToAssetsLibrary:self.movieFileOutputURL.copy];
self.movieFileOutputURL = nil;
}
}
#pragma mark Private Func 電影文件捕捉
/// 創建視頻文件臨時路徑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];
// 和圖片不同,視頻的寫入更耗時,所以寫入之前應該判斷是否能寫入
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 {
// 寫入成功 回調封面圖
[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];
// 設置maximumSize 寬為100,高為0 根據視頻的寬高比來計算圖片的高度
imageGenerator.maximumSize = CGSizeMake(100.0f, 0.0f);
// 捕捉視頻縮略圖會考慮視頻的變化(如視頻的方向變化),如果不設置,縮略圖的方向可能出錯
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.預覽視頻
previewView.session = captureManager.captureSession
原文鏈接:https://juejin.cn/post/7158405977400836104
相關推薦
- 2022-03-20 Oracle進階DECODE函數使用詳解_oracle
- 2022-05-18 python必備庫Matplotlib畫圖神器_python
- 2022-07-15 Android自定義view繪制表格的方法_Android
- 2022-05-06 Pandas?DataFrame數據修改值的方法_python
- 2023-03-15 k8s中pod使用詳解(云原生kubernetes)_云其它
- 2022-09-14 Android?實現卡片堆疊錢包管理動畫效果_Android
- 2022-03-17 c++智能指針unique_ptr的使用_C 語言
- 2022-03-06 css3溢出隱藏的方法_基礎教程
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支