在拍摄照片之前切换 AVCaptureSessionPreset
Switching AVCaptureSessionPreset before capturing photo
我正在制作实时相机滤镜应用。
我使用 AVCaptureVideoDataOutput
将 samplebuffer 传递给 MTKView
用于渲染预览和 AVCapturePhotoOutput
用于捕获照片。
而且我的应用程序有一些用于拍摄照片的纵横比选项。 (包括 16:9 和 4:3)
我想做的是以全屏大小进行预览(16:9,但更接近这个大小也可以),尽管用户 select 4:3 选项.
我打算在预览中显示边框线,以便用户可以计算出照片输出大小。
我需要 16:9 预设选项,如 1280*720 用于预览和照片预设选项用于拍摄照片。
我想出了一些点子。
有两个具有不同预设的 AVCaptureSessions
--> 不利于性能
使用 1280*720 预设拍摄并将照片输出裁剪为 4:3 纵横比 --> 低分辨率照片
在调用 photoOutput.capturePhoto
方法之前切换预设。 --> 预览瞬间冻结,因为 AVCaptureSession
必须更新
我决定使用 3,但它给了我一个错误。
(如果有更好的方法,请告诉我)
这是我的代码。
@IBAction func takePhoto(_ sender: UIButton) {
captureSessionQueue.async {
var photoSettings = AVCapturePhotoSettings()
photoSettings.isHighResolutionPhotoEnabled = true
// switch preset from .hd1280*720 to .photo
self.session.beginConfiguration()
if self.session.canSetSessionPreset(.photo) {
self.session.sessionPreset = .photo
}
self.session.commitConfiguration()
self.photoOutput.capturePhoto(with: photoSettings, delegate: self)
self.session.beginConfiguration()
self.session.sessionPreset = .hd1280*720
self.session.commitConfiguration()
}
}
错误是,
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could
not be completed" UserInfo={NSLocalizedFailureReason=An unknown error
occurred (-16800), NSLocalizedDescription=The operation could not be
completed, NSUnderlyingError=0x280013720 {Error
Domain=NSOSStatusErrorDomain Code=-16800 "(null)"}}
我认为这是因为我在会话完成对新预设的更新之前调用了 capturePhoto
方法。
当我在 commitConfiguration()
后 1 或 2 秒调用 self.photoOutput.capturePhoto
方法时,它起作用了。
那么,有什么方法可以让我知道 AVCaptureSession
更新的完成,或者有更好的解决方案来处理视频数据输出和照片输出之间的不同宽高比吗?
为视频和照片输出不同宽高比的最佳方式是在 AVCapturePhotoCaptureDelegate 委托中处理照片图像数据。您可以执行以下操作。在 didFinishProcessingPhoto 函数中,您裁剪图像并从中创建 jpeg 数据。您还需要将照片元数据复制到此 jpeg 数据中。然后在 didFinishCaptureFor 函数中,您可以将此数据保存到图片库。
class MyPhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate {
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
// crop image
if let cgImage = photo.cgImageRepresentation()?.takeUnretainedValue() {
let cropRect : CGRect = calculateAspectRatioCrop(cgImage:cgImage, aspectRatio:16.0/9.0)
if let cgCroppedImage = cgImage.cropping(to:cropRect) {
let image = UIImage(cgImage:cgCroppedImage)
if let photoData = image.jpegData(compressionQuality: 0.9) {
// add meta data from original image to cropped image data
self.photoData = addImageProperties(imageData: photoData, properties: photo.metadata as NSDictionary)
}
}
}
}
func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) {
if let error = error {
print("Error capturing photo: \(error)")
return
}
guard let photoData = photoData else {
print("No photo data resource")
return
}
savePhotoData()
}
func calculateAspectRatioCrop(cgImage : CGImage, aspectRatio: CGFloat) -> CGRect {
var width = CGFloat(cgImage.width)
var height = CGFloat(cgImage.height)
// should be cropped vertically or horizontally?
if aspectRatio > width/height {
height = width / aspectRatio
} else {
width = height * aspectRatio
}
return CGRect(x: (CGFloat(cgImage.width) - width)/2, y: (CGFloat(cgImage.height) - height)/2, width: width, height: height)
}
func addImageProperties(imageData: Data, properties: NSDictionary?) -> Data? {
// create an imagesourceref
if let source = CGImageSourceCreateWithData(imageData as CFData, nil) {
// this is of type image
if let uti = CGImageSourceGetType(source) {
// create a new data object and write the new image into it
let destinationData = NSMutableData()
if let destination = CGImageDestinationCreateWithData(destinationData, uti, 1, nil) {
// add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
CGImageDestinationAddImageFromSource(destination, source, 0, properties)
if CGImageDestinationFinalize(destination) == false {
return nil
}
return destinationData as Data
}
}
}
return nil
}
private var photoData : Data? = nil
}
我留给您填写 savePhotoData() 函数。
我正在制作实时相机滤镜应用。
我使用 AVCaptureVideoDataOutput
将 samplebuffer 传递给 MTKView
用于渲染预览和 AVCapturePhotoOutput
用于捕获照片。
而且我的应用程序有一些用于拍摄照片的纵横比选项。 (包括 16:9 和 4:3)
我想做的是以全屏大小进行预览(16:9,但更接近这个大小也可以),尽管用户 select 4:3 选项.
我打算在预览中显示边框线,以便用户可以计算出照片输出大小。
我需要 16:9 预设选项,如 1280*720 用于预览和照片预设选项用于拍摄照片。
我想出了一些点子。
有两个具有不同预设的
AVCaptureSessions
--> 不利于性能使用 1280*720 预设拍摄并将照片输出裁剪为 4:3 纵横比 --> 低分辨率照片
在调用
photoOutput.capturePhoto
方法之前切换预设。 --> 预览瞬间冻结,因为AVCaptureSession
必须更新
我决定使用 3,但它给了我一个错误。
(如果有更好的方法,请告诉我)
这是我的代码。
@IBAction func takePhoto(_ sender: UIButton) {
captureSessionQueue.async {
var photoSettings = AVCapturePhotoSettings()
photoSettings.isHighResolutionPhotoEnabled = true
// switch preset from .hd1280*720 to .photo
self.session.beginConfiguration()
if self.session.canSetSessionPreset(.photo) {
self.session.sessionPreset = .photo
}
self.session.commitConfiguration()
self.photoOutput.capturePhoto(with: photoSettings, delegate: self)
self.session.beginConfiguration()
self.session.sessionPreset = .hd1280*720
self.session.commitConfiguration()
}
}
错误是,
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSLocalizedFailureReason=An unknown error occurred (-16800), NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x280013720 {Error Domain=NSOSStatusErrorDomain Code=-16800 "(null)"}}
我认为这是因为我在会话完成对新预设的更新之前调用了 capturePhoto
方法。
当我在 commitConfiguration()
后 1 或 2 秒调用 self.photoOutput.capturePhoto
方法时,它起作用了。
那么,有什么方法可以让我知道 AVCaptureSession
更新的完成,或者有更好的解决方案来处理视频数据输出和照片输出之间的不同宽高比吗?
为视频和照片输出不同宽高比的最佳方式是在 AVCapturePhotoCaptureDelegate 委托中处理照片图像数据。您可以执行以下操作。在 didFinishProcessingPhoto 函数中,您裁剪图像并从中创建 jpeg 数据。您还需要将照片元数据复制到此 jpeg 数据中。然后在 didFinishCaptureFor 函数中,您可以将此数据保存到图片库。
class MyPhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate {
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
// crop image
if let cgImage = photo.cgImageRepresentation()?.takeUnretainedValue() {
let cropRect : CGRect = calculateAspectRatioCrop(cgImage:cgImage, aspectRatio:16.0/9.0)
if let cgCroppedImage = cgImage.cropping(to:cropRect) {
let image = UIImage(cgImage:cgCroppedImage)
if let photoData = image.jpegData(compressionQuality: 0.9) {
// add meta data from original image to cropped image data
self.photoData = addImageProperties(imageData: photoData, properties: photo.metadata as NSDictionary)
}
}
}
}
func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) {
if let error = error {
print("Error capturing photo: \(error)")
return
}
guard let photoData = photoData else {
print("No photo data resource")
return
}
savePhotoData()
}
func calculateAspectRatioCrop(cgImage : CGImage, aspectRatio: CGFloat) -> CGRect {
var width = CGFloat(cgImage.width)
var height = CGFloat(cgImage.height)
// should be cropped vertically or horizontally?
if aspectRatio > width/height {
height = width / aspectRatio
} else {
width = height * aspectRatio
}
return CGRect(x: (CGFloat(cgImage.width) - width)/2, y: (CGFloat(cgImage.height) - height)/2, width: width, height: height)
}
func addImageProperties(imageData: Data, properties: NSDictionary?) -> Data? {
// create an imagesourceref
if let source = CGImageSourceCreateWithData(imageData as CFData, nil) {
// this is of type image
if let uti = CGImageSourceGetType(source) {
// create a new data object and write the new image into it
let destinationData = NSMutableData()
if let destination = CGImageDestinationCreateWithData(destinationData, uti, 1, nil) {
// add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
CGImageDestinationAddImageFromSource(destination, source, 0, properties)
if CGImageDestinationFinalize(destination) == false {
return nil
}
return destinationData as Data
}
}
}
return nil
}
private var photoData : Data? = nil
}
我留给您填写 savePhotoData() 函数。