iOS音視訊(一) -- AVFoundation捕捉
阿新 • • 發佈:2020-06-24
一、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、配置會話
- 建立session會話
- 設定解析度
- 建立捕捉裝置
- 將捕捉裝置轉化為捕捉裝置輸入
- 將捕捉裝置輸入新增到會話中(新增過程需要注意是否能夠新增進會話中)
- 配置捕捉裝置的輸出
- 將捕捉裝置輸出新增到會話中(新增過程需要注意是否能夠新增進會話中)
#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、聚焦
- 拿到當前裝置
- 需要判斷裝置是否支援對焦等。
- 配置時不能讓多個物件對他進行更改,所以鎖定該裝置
- 設定對焦點、對焦模式等。
- 解鎖裝置
/// 詢問當前活躍的攝像頭是否支援興趣點對焦
- (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、曝光
- 拿到當前活躍裝置
- 建立曝光mode
- 判斷裝置是否支援指定的模式
- 鎖定該裝置
- 設定曝光點、曝光模式
- 判斷是否支援鎖定曝光
- 支援則使用KVO去設定狀態
- 監聽回撥,獲取裝置
- 判斷是否支援曝光
- 移除觀察者,修改裝置的曝光模式
- 解鎖裝置
- 對外聲稱一個重設對焦曝光的介面
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、拍照
- 對外暴露拍照介面 captureStillImage
- 對圖片輸出設定setting和代理
- 通過代理獲取到圖片的data資料
- 將圖片的data資料轉化為UIImage
- 將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];
});
}];
}
複製程式碼