Configure a Capture Session

AVCaptureSession接受来自摄像头和麦克风等捕捉设备的输入数据。接收到输入后,AVCaptureSession将该数据封送到适当的输出进行处理,最终生成电影文件或静态照片。配置捕获会话的输入和输出后,您会告诉它开始捕获,然后停止捕获。

private let session = AVCaptureSession()

AVCam默认选择后摄像头,并将摄像头捕获会话配置为将内容流式传输到视频预览视图。PreviewView是由AVCaptureVideoPreviewLayer支持的自定义UIView子类。AVFoundation没有PreviewView类,但示例代码创建了一个类以方便会话管理。
下图显示了会话如何管理输入设备和捕获输出:

将与AVCaptureSession的任何交互(包括其输入和输出)委托给专用串行调度队列(sessionQueue),以便交互不会阻塞主队列。执行任何涉及更改会话拓扑或中断其在单独调度队列上运行的视频流的配置,因为在队列处理更改之前,会话配置始终会阻止其他任务的执行。类似地,示例代码会分派其他任务,例如恢复中断的会话、切换捕获模式、切换相机以及将媒体写入会话队列中的文件,以便它们的处理不会阻止或延迟用户与应用程序的交互。
相反,代码将影响UI的任务(例如更新预览视图)分派到主队列,因为CALayer的子类AVCaptureVideoPreviewLayer是示例预览视图的支持层。您必须在主线程上操作UIView子类,以使它们及时、交互式地显示出来。
在viewDidLoad中,AVCam创建会话并将其分配给预览视图:

previewView.session = session

请求访问输入设备的授权

配置会话后,它就可以接受输入了。无论是相机还是麦克风,每个AVCaptureDevice都需要用户授权访问。AVFoundation使用AVAuthorizationStatus枚举授权状态,该状态通知应用程序用户是否限制或拒绝访问捕获设备。
有关为自定义授权请求准备应用程序的Info.plist的更多信息,请参阅在iOS上请求媒体捕获授权。

在后向和前向摄像头之间切换

changeCamera方法在用户点击UI中的按钮时处理相机之间的切换。它使用发现会话,该会话按首选项的顺序列出可用的设备类型,并接受其设备阵列中的第一个设备。例如,AVCam中的videoDeviceDiscoverySession查询应用程序运行的设备是否有可用的输入设备。此外,如果用户的设备有损坏的摄像头,那么它在设备阵列中不可用。

switch currentPosition {case .unspecified, .front:newVideoDevice = backVideoDeviceDiscoverySession.devices.firstcase .back:newVideoDevice = frontVideoDeviceDiscoverySession.devices.first@unknown default:print("Unknown capture position. Defaulting to back, dual-camera.")newVideoDevice = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: .back)
}

如果发现会话在正确的位置找到相机,它将从捕获会话中删除以前的输入,并添加新相机作为输入。

// Remove the existing device input first, because AVCaptureSession doesn't support
// simultaneous use of the rear and front cameras.
self.session.removeInput(self.videoDeviceInput)if self.session.canAddInput(videoDeviceInput) {NotificationCenter.default.removeObserver(self, name: .AVCaptureDeviceSubjectAreaDidChange, object: currentVideoDevice)NotificationCenter.default.addObserver(self, selector: #selector(self.subjectAreaDidChange), name: .AVCaptureDeviceSubjectAreaDidChange, object: videoDeviceInput.device)self.session.addInput(videoDeviceInput)self.videoDeviceInput = videoDeviceInput
} else {self.session.addInput(self.videoDeviceInput)
}

处理中断和错误

在捕获会话期间,可能会发生中断,例如电话呼叫、来自其他应用程序的通知和音乐播放。通过添加观察员以侦听AVCaptureSessionWasInterruptedNotification来处理这些中断:

NotificationCenter.default.addObserver(self,selector: #selector(sessionWasInterrupted),name: .AVCaptureSessionWasInterrupted,object: session)
NotificationCenter.default.addObserver(self,selector: #selector(sessionInterruptionEnded),name: .AVCaptureSessionInterruptionEnded,object: session)

当AVCam收到中断通知时,它可以暂停或暂停会话,并在中断结束时选择恢复活动。AVCam将sessionWasInterrupted注册为接收通知的处理程序,以便在捕获会话中断时通知用户:

if reason == .audioDeviceInUseByAnotherClient || reason == .videoDeviceInUseByAnotherClient {showResumeButton = true
} else if reason == .videoDeviceNotAvailableWithMultipleForegroundApps {// Fade-in a label to inform the user that the camera is unavailable.cameraUnavailableLabel.alpha = 0cameraUnavailableLabel.isHidden = falseUIView.animate(withDuration: 0.25) {self.cameraUnavailableLabel.alpha = 1}
} else if reason == .videoDeviceNotAvailableDueToSystemPressure {print("Session stopped running due to shutdown system pressure level.")
}

摄像机视图控制器观察AVCaptureSessionRuntimeError以在发生错误时接收通知:

NotificationCenter.default.addObserver(self,selector: #selector(sessionRuntimeError),name: .AVCaptureSessionRuntimeError,object: session)

发生运行时错误时,请重新启动捕获会话:

// If media services were reset, and the last start succeeded, restart the session.
if error.code == .mediaServicesWereReset {sessionQueue.async {if self.isSessionRunning {self.session.startRunning()self.isSessionRunning = self.session.isRunning} else {DispatchQueue.main.async {self.resumeButton.isHidden = false}}}
} else {resumeButton.isHidden = false
}

如果设备承受系统压力,如过热,捕获会话也可能停止。相机本身不会降低拍摄质量或掉帧;如果达到临界点,相机将停止工作,或者设备将关闭。为了避免让用户感到意外,您可能希望应用程序根据AVCaptureSystemPressureState的反馈手动降低帧速率、关闭深度或调节性能:

let pressureLevel = systemPressureState.level
if pressureLevel == .serious || pressureLevel == .critical {if self.movieFileOutput == nil || self.movieFileOutput?.isRecording == false {do {try self.videoDeviceInput.device.lockForConfiguration()print("WARNING: Reached elevated system pressure level: \(pressureLevel). Throttling frame rate.")self.videoDeviceInput.device.activeVideoMinFrameDuration = CMTime(value: 1, timescale: 20)self.videoDeviceInput.device.activeVideoMaxFrameDuration = CMTime(value: 1, timescale: 15)self.videoDeviceInput.device.unlockForConfiguration()} catch {print("Could not lock device for configuration: \(error)")}}
} else if pressureLevel == .shutdown {print("Session stopped running due to shutdown system pressure level.")
}

拍照

拍照发生在会话队列上。该过程首先更新AVCapturePhotoOutput连接,以匹配视频预览层的视频方向。这使摄像头能够准确捕捉用户在屏幕上看到的内容:

if let photoOutputConnection = self.photoOutput.connection(with: .video) {photoOutputConnection.videoOrientation = videoPreviewLayerOrientation!
}

对齐输出后,AVCam继续创建AVCapturePhotoSettings,以配置捕获参数,如焦距、闪光灯和分辨率:

var photoSettings = AVCapturePhotoSettings()// Capture HEIF photos when supported. Enable auto-flash and high-resolution photos.
if  self.photoOutput.availablePhotoCodecTypes.contains(.hevc) {photoSettings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.hevc])
}if self.videoDeviceInput.device.isFlashAvailable {photoSettings.flashMode = .auto
}photoSettings.isHighResolutionPhotoEnabled = true
if let previewPhotoPixelFormatType = photoSettings.availablePreviewPhotoPixelFormatTypes.first {photoSettings.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: previewPhotoPixelFormatType]
}
// Live Photo capture is not supported in movie mode.
if self.livePhotoMode == .on && self.photoOutput.isLivePhotoCaptureSupported {let livePhotoMovieFileName = NSUUID().uuidStringlet livePhotoMovieFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent((livePhotoMovieFileName as NSString).appendingPathExtension("mov")!)photoSettings.livePhotoMovieFileURL = URL(fileURLWithPath: livePhotoMovieFilePath)
}photoSettings.isDepthDataDeliveryEnabled = (self.depthDataDeliveryMode == .on&& self.photoOutput.isDepthDataDeliveryEnabled)photoSettings.isPortraitEffectsMatteDeliveryEnabled = (self.portraitEffectsMatteDeliveryMode == .on&& self.photoOutput.isPortraitEffectsMatteDeliveryEnabled)if photoSettings.isDepthDataDeliveryEnabled {if !self.photoOutput.availableSemanticSegmentationMatteTypes.isEmpty {photoSettings.enabledSemanticSegmentationMatteTypes = self.selectedSemanticSegmentationMatteTypes}
}photoSettings.photoQualityPrioritization = self.photoQualityPrioritizationMode

该示例使用一个单独的对象PhotoCaptureProcessor作为照片捕获委托,以隔离每个捕获生命周期。这种清晰的捕获周期分离对于实时照片是必要的,因为单个捕获周期可能涉及多个帧的捕获。
每次用户按下中央快门按钮时,AVCam通过调用capturePhotoWithSettings:delegate:,以先前配置的设置捕获一张照片:

self.photoOutput.capturePhoto(with: photoSettings, delegate: photoCaptureProcessor)

capturePhoto方法接受两个参数:

AVCapturePhotoSettings对象,封装用户通过应用程序配置的设置,如曝光、闪光灯、焦距和火炬。
符合AVCapturePhotoCaptureDelegate协议的委托,用于响应系统在照片捕获期间提供的后续回调。
一旦应用程序调用capturePhotoWithSettings:delegate:,开始摄影的过程就结束了。从那时起,对单个照片捕获的操作将在代理回调中发生。

通过照片捕获代理跟踪结果

capturePhoto方法仅开始拍照过程。流程的其余部分发生在应用程序实现的委托方法中。

captureOutput:willBeginCaptureForResolvedSettings:在调用capturePhoto时首先到达。解析的设置表示相机将应用于即将拍摄的照片的实际设置。AVCam仅对特定于实时照片的行为使用此方法。AVCam通过检查照片的livePhotoMovieDimensions大小来判断照片是否为实况照片;如果照片是实时照片,AVCam会增加计数以跟踪正在进行的实时照片:

self.sessionQueue.async {if capturing {self.inProgressLivePhotoCapturesCount += 1} else {self.inProgressLivePhotoCapturesCount -= 1}let inProgressLivePhotoCapturesCount = self.inProgressLivePhotoCapturesCountDispatchQueue.main.async {if inProgressLivePhotoCapturesCount > 0 {self.capturingLivePhotoLabel.isHidden = false} else if inProgressLivePhotoCapturesCount == 0 {self.capturingLivePhotoLabel.isHidden = true} else {print("Error: In progress Live Photo capture count is less than 0.")}}
}

captureOutput:willCapturePhotoForResolvedSettings:在系统播放快门声音后立即到达。AVCam利用此机会闪烁屏幕,提醒用户相机拍摄到照片。示例代码通过将预览视图层的不透明度从0设置为1来实现此flash。

// Flash the screen to signal that AVCam took a photo.
DispatchQueue.main.async {self.previewView.videoPreviewLayer.opacity = 0UIView.animate(withDuration: 0.25) {self.previewView.videoPreviewLayer.opacity = 1}
}

captureOutput:didFinishProcessingPhoto:error:在系统完成深度数据和肖像效果蒙版处理时到达。AVCam在此阶段检查肖像效果蒙版和深度元数据:

// A portrait effects matte gets generated only if AVFoundation detects a face.
if var portraitEffectsMatte = photo.portraitEffectsMatte {if let orientation = photo.metadata[ String(kCGImagePropertyOrientation) ] as? UInt32 {portraitEffectsMatte = portraitEffectsMatte.applyingExifOrientation(CGImagePropertyOrientation(rawValue: orientation)!)}let portraitEffectsMattePixelBuffer = portraitEffectsMatte.mattingImagelet portraitEffectsMatteImage = CIImage( cvImageBuffer: portraitEffectsMattePixelBuffer, options: [ .auxiliaryPortraitEffectsMatte: true ] )

captureOutput:didFinishCaptureForResolvedSettings:error:是最后的回调,标志着单个照片的捕获结束。AVCam将清理其代理和设置,以便它们不会保留在后续照片捕获中:

self.sessionQueue.async {self.inProgressPhotoCaptureDelegates[photoCaptureProcessor.requestedPhotoSettings.uniqueID] = nil
}

您可以在此代理方法中应用其他视觉效果,例如设置捕获照片的预览缩略图的动画。
有关通过代理回调跟踪照片进度的更多信息,请参阅跟踪照片捕获进度。

拍摄现场照片

启用实时照片捕获时,相机会在捕获瞬间拍摄一张静止图像和一段短片。该应用程序触发实时照片捕获的方式与静态照片捕获的方式相同:只需调用capturePhotoWithSettings,即可通过LivePhotoMovieUrl属性传递实时照片短视频的URL。您可以在AVCapturePhotoOutput级别启用实时照片,也可以在AVCapturePhotoSettings级别按每次捕获配置实时照片。
由于Live Photo capture会创建一个简短的电影文件,因此AVCam必须表示将电影文件保存为URL的位置。此外,由于实时照片捕获可能重叠,因此代码必须跟踪正在进行的实时照片捕获的数量,以确保在这些捕获过程中实时照片标签保持可见。上一节中的photoOutput(ux0:willBeginCaptureFor:)委托方法实现此跟踪计数器。

captureOutput:DidFinishRecordingLivePhotoMovieForeventTualFile特性:resolvedSettings:短片录制结束时激发。阿夫卡姆在这里解散了现场徽章。由于相机已完成短片的录制,AVCam执行实时照片处理程序,使完成计数器递减:

livePhotoCaptureHandler(false)

captureOutput:didFinishProcessingLivePhotoToMovieFileAtURL:duration:photoDisplayTime:resolvedSettings:error:最后触发,表示电影已完全写入磁盘并准备好使用。AVCam利用此机会显示任何捕获错误,并将保存的文件URL重定向到其最终输出位置:

if error != nil {print("Error processing Live Photo companion movie: \(String(describing: error))")return
}
livePhotoCompanionMovieURL = outputFileURL

有关将实时照片捕获整合到应用程序中的更多信息,请参阅捕获静态和实时照片。

捕获深度数据和人像效果蒙版

使用AVCapturePhotoOutput,AVCam查询捕获设备,查看其配置是否可以向静止图像提供深度数据和肖像效果蒙版。如果输入设备支持这两种模式中的任何一种,并且您在“捕获设置”中启用了它们,则相机会根据每个照片请求将“深度”和“纵向效果蒙版”附加为辅助元数据。如果设备支持传送深度数据、人像效果蒙版或实时照片,应用程序将显示一个按钮,用于切换启用或禁用该功能的设置。

  if self.photoOutput.isDepthDataDeliverySupported {self.photoOutput.isDepthDataDeliveryEnabled = trueDispatchQueue.main.async {self.depthDataDeliveryButton.isEnabled = true}}if self.photoOutput.isPortraitEffectsMatteDeliverySupported {self.photoOutput.isPortraitEffectsMatteDeliveryEnabled = trueDispatchQueue.main.async {self.portraitEffectsMatteDeliveryButton.isEnabled = true}}if !self.photoOutput.availableSemanticSegmentationMatteTypes.isEmpty {self.photoOutput.enabledSemanticSegmentationMatteTypes = self.photoOutput.availableSemanticSegmentationMatteTypesself.selectedSemanticSegmentationMatteTypes = self.photoOutput.availableSemanticSegmentationMatteTypesDispatchQueue.main.async {self.semanticSegmentationMatteDeliveryButton.isEnabled = (self.depthDataDeliveryMode == .on) ? true : false}}DispatchQueue.main.async {self.livePhotoModeButton.isHidden = falseself.depthDataDeliveryButton.isHidden = falseself.portraitEffectsMatteDeliveryButton.isHidden = falseself.semanticSegmentationMatteDeliveryButton.isHidden = falseself.photoQualityPrioritizationSegControl.isHidden = falseself.photoQualityPrioritizationSegControl.isEnabled = true}

相机将深度和纵向效果蒙版元数据存储为辅助图像,可通过图像I/O API发现和寻址。AVCam通过搜索类型为KCGIMAGEAUXILIARYDATATETAPERATIVETRATIONEFFECTSMATTE的辅助图像来访问此元数据:

if var portraitEffectsMatte = photo.portraitEffectsMatte {if let orientation = photo.metadata[String(kCGImagePropertyOrientation)] as? UInt32 {portraitEffectsMatte = portraitEffectsMatte.applyingExifOrientation(CGImagePropertyOrientation(rawValue: orientation)!)}let portraitEffectsMattePixelBuffer = portraitEffectsMatte.mattingImage

有关深度数据捕获的更多信息,请参阅使用深度捕获照片。

捕获语义切分模板

使用AVCapturePhotoOutput,AVCam还可以捕获语义分割蒙版,将人的头发、皮肤和牙齿分割成不同的蒙版图像。将这些辅助图像与主照片一起捕获的功能简化了照片效果的应用,例如改变一个人的头发颜色或使他们的微笑变亮。
通过将照片输出的enabledSemanticSegmentationMatteTypes属性设置为首选值(AVSemanticSegmentationMatteTypeHair、AVSemanticSegmentationMatteTypeSkin和AVSemanticSegmentationMatteTypeDeaths),可以捕获这些辅助图像。要捕获所有支持的类型,请将此属性设置为与照片输出的AvailableManticSegmentationMatteTypes属性匹配。

// Capture all available semantic segmentation matte types.
photoOutput.enabledSemanticSegmentationMatteTypes = photoOutput.availableSemanticSegmentationMatteTypes

照片输出完成捕获照片后,通过查询照片的semanticSegmentationMatteForType:方法检索关联的分段蒙版图像。此方法返回一个AVSemanticSegmentationMatte,其中包含matte图像和处理图像时可以使用的其他元数据。示例应用程序将语义分割蒙版图像的数据添加到一个数组中,以便您可以将其写入用户的照片库。

// Find the semantic segmentation matte image for the specified type.
guard var segmentationMatte = photo.semanticSegmentationMatte(for: ssmType) else { return }// Retrieve the photo orientation and apply it to the matte image.
if let orientation = photo.metadata[String(kCGImagePropertyOrientation)] as? UInt32,let exifOrientation = CGImagePropertyOrientation(rawValue: orientation) {// Apply the Exif orientation to the matte image.segmentationMatte = segmentationMatte.applyingExifOrientation(exifOrientation)
}var imageOption: CIImageOption!// Switch on the AVSemanticSegmentationMatteType value.
switch ssmType {case .hair:imageOption = .auxiliarySemanticSegmentationHairMatte
case .skin:imageOption = .auxiliarySemanticSegmentationSkinMatte
case .teeth:imageOption = .auxiliarySemanticSegmentationTeethMatte
case .glasses:imageOption = .auxiliarySemanticSegmentationGlassesMatte
default:print("This semantic segmentation type is not supported!")return
}guard let perceptualColorSpace = CGColorSpace(name: CGColorSpace.sRGB) else { return }// Create a new CIImage from the matte's underlying CVPixelBuffer.
let ciImage = CIImage( cvImageBuffer: segmentationMatte.mattingImage,options: [imageOption: true,.colorSpace: perceptualColorSpace])// Get the HEIF representation of this image.
guard let imageData = context.heifRepresentation(of: ciImage,format: .RGBA8,colorSpace: perceptualColorSpace,options: [.depthImage: ciImage]) else { return }// Add the image data to the SSM data array for writing to the photo library.
semanticSegmentationMatteDataArray.append(imageData)

将照片保存到用户的照片库
在将图像或电影保存到用户的照片库之前,必须首先请求访问该库。请求写入授权的过程镜像了捕获设备授权:显示带有您在Info.plist中提供的文本的警报。
AVCam检查captureOutput:DidFinishRecordingToOutputFileAttribute:fromConnections:error:callback方法中的授权,AVCaptureOutput在该方法中提供媒体数据以另存为输出。

PHPhotoLibrary.requestAuthorization { status in

有关请求访问用户照片库的更多信息,请参阅在照片应用程序中提供增强的隐私体验。
录制电影文件
AVCam通过使用.video限定符查询和添加输入设备来支持视频捕获。应用程序默认为后双摄像头,但如果设备没有双摄像头,应用程序默认为广角摄像头。

if let dualCameraDevice = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: .back) {defaultVideoDevice = dualCameraDevice
} else if let dualWideCameraDevice = AVCaptureDevice.default(.builtInDualWideCamera, for: .video, position: .back) {// If a rear dual camera is not available, default to the rear dual wide camera.defaultVideoDevice = dualWideCameraDevice
} else if let backCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) {// If a rear dual wide camera is not available, default to the rear wide angle camera.defaultVideoDevice = backCameraDevice
} else if let frontCameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) {// If the rear wide angle camera isn't available, default to the front wide angle camera.defaultVideoDevice = frontCameraDevice
}

不要像静态照片那样将设置传递给系统,而是像实时照片一样传递输出URL。委托回调提供相同的URL,因此您的应用程序不需要将其存储在中间变量中。
一旦用户点击Record开始捕获,AVCam将调用startRecordingToOutputFileURL:recordingDelegate::

movieFileOutput.startRecording(to: URL(fileURLWithPath: outputFilePath), recordingDelegate: self)

就像capturePhoto触发的用于静态捕获的代理回调一样,startRecording触发一系列用于电影录制的代理回调。

通过代理回调链跟踪电影录制的进度。实现AVCaptureFileOutputRecordingDelegate而不是实现AVCapturePhotoCaptureDelegate。由于电影录制代理回调需要与捕获会话交互,AVCam使CameraViewController成为代理,而不是创建单独的代理对象。
captureOutput:DidStartRecordingToOutputFileAttribute:fromConnections:在文件输出开始向文件写入数据时激发。AVCam利用此机会将记录按钮更改为停止按钮:

DispatchQueue.main.async {self.recordButton.isEnabled = trueself.recordButton.setImage(#imageLiteral(resourceName: "CaptureStop"), for: [])
}

captureOutput:DidFinishRedingToOutputFileATURL:fromConnections:error:最后触发,表示电影已完全写入磁盘并准备好使用。AVCam借此机会将临时保存的电影从给定URL移动到用户的照片库或应用程序的文档文件夹:

 PHPhotoLibrary.shared().performChanges({let options = PHAssetResourceCreationOptions()options.shouldMoveFile = truelet creationRequest = PHAssetCreationRequest.forAsset()creationRequest.addResource(with: .video, fileURL: outputFileURL, options: options)// Specify the location the movie was recoreded
creationRequest.location = self.locationManager.location}, completionHandler: { success, error inif !success {print("AVCam couldn't save the movie to your photo library: \(String(describing: error))")}cleanup()})

如果AVCam进入后台,例如当用户接听来电时,应用程序必须请求用户允许继续录制。AVCam通过后台任务从系统请求时间来执行此保存。此后台任务确保有足够的时间将文件写入照片库,即使AVCam后退到后台。为了结束后台执行,AVCam在保存记录的文件后调用captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:中的endBackgroundTask:。

self.backgroundRecordingID = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)

在录制电影时拍照
与iOS摄像头应用程序一样,AVCam可以在拍摄电影的同时拍照。AVCam以与视频相同的分辨率捕获此类照片。

let movieFileOutput = AVCaptureMovieFileOutput()if self.session.canAddOutput(movieFileOutput) {self.session.beginConfiguration()self.session.addOutput(movieFileOutput)self.session.sessionPreset = .highself.selectedMovieMode10BitDeviceFormat = self.tenBitVariantOfFormat(activeFormat: self.videoDeviceInput.device.activeFormat)if self.selectedMovieMode10BitDeviceFormat != nil {DispatchQueue.main.async {self.HDRVideoModeButton.isHidden = falseself.HDRVideoModeButton.isEnabled = true}if self.HDRVideoMode == .on {do {try self.videoDeviceInput.device.lockForConfiguration()self.videoDeviceInput.device.activeFormat = self.selectedMovieMode10BitDeviceFormat!print("Setting 'x420' format \(String(describing: self.selectedMovieMode10BitDeviceFormat)) for video recording")self.videoDeviceInput.device.unlockForConfiguration()} catch {print("Could not lock device for configuration: \(error)")}}}if let connection = movieFileOutput.connection(with: .video) {if connection.isVideoStabilizationSupported {connection.preferredVideoStabilizationMode = .auto}}self.session.commitConfiguration()DispatchQueue.main.async {captureModeControl.isEnabled = true}self.movieFileOutput = movieFileOutputDispatchQueue.main.async {self.recordButton.isEnabled = true/*For photo captures during movie recording, Balanced quality photo processing is prioritizedto get high quality stills and avoid frame drops during recording.*/self.photoQualityPrioritizationSegControl.selectedSegmentIndex = 1self.photoQualityPrioritizationSegControl.sendActions(for: UIControl.Event.valueChanged)}
}

相机AVCam: Building a Camera App相关推荐

  1. Camera APP 问题集锦

    1.AE trigger & AF trigger同一帧下,precapture在flashback后导致打闪拍照过曝 [DESCRIPTION] AE trigger & AF tr ...

  2. android camera慢动作,motion camera app安卓版

    motion camera app安卓版这是一款可以拍摄慢动作的相机软件,该软件简单实用.功能强大,能够完美运行在安卓系统上,可以在手机上拍出高清的慢动作视频,同时还支持音视频合成剪辑处理等功能,选择 ...

  3. 相机模型-Extended Unified Camera Model

    相机模型-Extended Unified Camera Model 模型介绍 投影过程 反投影过程 雅可比计算 之前讲到了Unified Camera Model模型,该模型是借助于一个虚拟的单位球 ...

  4. 单像素相机(single pixel camera,SPC)

    单像素相机(single pixel camera,SPC) 在我学习的领域(利用单像素探测器实现无成像的目标识别/分类-),经常接触到的是像单像素相机这种的成像系统(光源将物体的像投射到DMD上进行 ...

  5. Android 创建自己的Camera App

    在sdk中找到/sdk/docs/guide/topics/media/camera.html#custom-camera,里面有详细的api参考 在清单文件中添加相应的权限: <uses-pe ...

  6. Camera APP(预览、拍照、录像)

    一.API:Application Programming Interface,应用程序接口,是一些预先定义的接口(如函数.HTTP接口),或指软件系统不同组成部分衔接的约定.用于提供应用程序与开发人 ...

  7. android使用自定义相机避开部分小米手机app调用系统相机有水印会转向的问题

    1.需求 我们要求很简单,就是拍照后显示效果要横屏拍的横着显示,竖屏拍着竖屏显示.但是我的手机小米5x等小米型号,存在横竖使用系统相机拍摄都是横屏显示的问题.更惨的是获取旋转角度什么的始终是0,没办法 ...

  8. 相机模型--Catadioptric Omnidirectional Camera

    Catadioptric Omnidirectional Camera CVPR97 Abstract 摘要 传统相机具有有限的视野(limited fields of view)使其在视觉应用中受到 ...

  9. Android doc |Getting Started|部分 部分译文 --Building Your First App

    Building a Simple User Interface Android App的图形用户界面是由一层层的View和ViewGroup对象建立起来的.View对象一般是UI控件(widgets ...

最新文章

  1. ViewPager 的点击事件回调
  2. python参数传递方法_深入理解python中函数传递参数是值传递还是引用传递
  3. BZOJ - 3963: [WF2011]MachineWorks
  4. 大数据分析中国冬季重度雾霾的成因(三)
  5. ++和--操作符分析
  6. 在存储器的层次结构里,谁最快,谁最贵,谁最大?
  7. Python项目:用微信自动给女朋友每天一句英语问候
  8. 给定一个字符串str,将str中连续两个字符为a的字符替换为b(一个或连续超过多个字符a则不替换)...
  9. 有了net send,谁还用IM?
  10. java定义用户类_用户定义的值类在Java中看起来像什么?
  11. 三层交换机和链路聚合
  12. x61 linux 驱动下载,ThinkPad T61/X61换XP系统及驱动下载
  13. matlab如何调用swmm,一套基于SWMM开放的城市管网系统控制设计
  14. 32位qt程序, 利用32位mysql驱动,连接64位mysql8.0
  15. Python数据分析(二) —— 进阶绘制双折线图
  16. 由Table_locks_waited想到的mysql 表锁问题
  17. 《大明王朝》雪崩前,精英们的狂欢
  18. 数据结构 课程设计报告
  19. swift 自制framework中加载nib
  20. 你的圈子,已经暴露了你的阶层

热门文章

  1. 2022半导体芯片人才市场趋势报告
  2. 数据库启动关闭有关的SCN
  3. java如何实现多线程_Java中实现多线程的两种方式
  4. 「解读」华为云桌面说“高清”的时候,究竟在说什么?
  5. 我的科研生活2017-3-18
  6. JAVA星之语明星周边产品销售网站计算机毕业设计Mybatis+系统+数据库+调试部署
  7. redist mysql_SQL Redist content: Command line option syntax error. Type C
  8. QT打包时系统提示 Cannot find Visual Studio redist directory
  9. 高斯模糊效果实现方案及性能对比
  10. 服务器Linux系统安全升级