I'm trying to get frame like VideoCapture in opencv using swiftUI
here is my code which is not working
I expected that "Got a frame!" should print in console when each frame from camera is available
at least, I can see the preview on the screen
How can I solve it or is there an any other idea?
If you have some reference to do same thing, It would be helpful
// For camera streaming
struct DemoVideoStreaming: View {
var body: some View {
VStack {
PreviewHolder()
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
}
}
struct PreviewHolder: UIViewRepresentable {
func makeUIView(context: UIViewRepresentableContext<PreviewHolder>) -> PreviewView {
PreviewView()
}
func updateUIView(_ uiView: PreviewView, context: UIViewRepresentableContext<PreviewHolder>) {
}
typealias UIViewType = PreviewView
}
class PreviewView: UIView, AVCaptureVideoDataOutputSampleBufferDelegate {
private var captureSession: AVCaptureSession?
init() {
super.init(frame: .zero)
var allowedAccess = false
let blocker = DispatchGroup()
blocker.enter()
AVCaptureDevice.requestAccess(for: .video) { flag in
allowedAccess = flag
blocker.leave()
}
blocker.wait()
if !allowedAccess {
print("!!! NO ACCESS TO CAMERA")
return
}
// setup session
// Step 3. AVCaptureSession
let session = AVCaptureSession()
session.beginConfiguration()
let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera,
for : .video, position: .front) //alternate AVCaptureDevice.default(for: .video)
guard videoDevice != nil, let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice!), session.canAddInput(videoDeviceInput) else {
print("!!! NO CAMERA DETECTED")
return
}
session.addInput(videoDeviceInput)
let videoOutput = AVCaptureVideoDataOutput()
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "sample buffer"))
guard session.canAddOutput(videoOutput) else { return }
session.addOutput(videoOutput)
session.commitConfiguration()
self.captureSession = session
}
override class var layerClass: AnyClass {
AVCaptureVideoPreviewLayer.self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var videoPreviewLayer: AVCaptureVideoPreviewLayer {
return layer as! AVCaptureVideoPreviewLayer
}
// wrapping dispatch
override func didMoveToSuperview() {
super.didMoveToSuperview()
if nil != self.superview {
self.videoPreviewLayer.session = self.captureSession
self.videoPreviewLayer.videoGravity = .resizeAspect
self.captureSession?.startRunning()
} else {
self.captureSession?.stopRunning()
}
}
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) {
print("Got a frame!")
}
}
that is because you didn't sign the AVCaptureVideoDataOutputSampleBufferDelegate delegate to your code
here is how should it look like
import UIKit
import AVFoundation
final class PreviewView: UIView ,AVCaptureVideoDataOutputSampleBufferDelegate{
private var captureSession: AVCaptureSession?
var imageBufferHandler: ImageBufferHandler?
private var videoConnection: AVCaptureConnection!
init() {
super.init(frame: .zero)
var allowedAccess = false
let blocker = DispatchGroup()
blocker.enter()
AVCaptureDevice.requestAccess(for: .video) { flag in
allowedAccess = flag
blocker.leave()
}
blocker.wait()
if !allowedAccess {
print("!!! NO ACCESS TO CAMERA")
return
}
// setup session
let session = AVCaptureSession()
session.beginConfiguration()
let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera,
for: .video, position: .unspecified) //alternate AVCaptureDevice.default(for: .video)
guard videoDevice != nil, let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice!), session.canAddInput(videoDeviceInput) else {
print("!!! NO CAMERA DETECTED")
return
}
session.addInput(videoDeviceInput)
session.commitConfiguration()
self.captureSession = session
// setup video output
let videoDataOutput = AVCaptureVideoDataOutput()
videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey : NSNumber(value: kCVPixelFormatType_32BGRA)] as [String : Any]
videoDataOutput.alwaysDiscardsLateVideoFrames = true
let queue = DispatchQueue(label: "com.queue.videosamplequeue")
videoDataOutput.setSampleBufferDelegate(self, queue: queue)
guard captureSession!.canAddOutput(videoDataOutput) else {
fatalError()
}
captureSession!.addOutput(videoDataOutput)
videoConnection = videoDataOutput.connection(with: .video)
}
override class var layerClass: AnyClass {
AVCaptureVideoPreviewLayer.self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var videoPreviewLayer: AVCaptureVideoPreviewLayer {
return layer as! AVCaptureVideoPreviewLayer
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
if nil != self.superview {
self.videoPreviewLayer.session = self.captureSession
self.videoPreviewLayer.videoGravity = .resize
self.captureSession?.startRunning()
}
else {
self.captureSession?.stopRunning()
}
}
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
print("Got a frame!")
if connection.videoOrientation != .portrait {
connection.videoOrientation = .portrait
return
}
if let imageBufferHandler = imageBufferHandler
{
imageBufferHandler(sampleBuffer)
}
}}
don't forget to add the image buffer handler
typealias ImageBufferHandler = ((_ imageBuffer: CMSampleBuffer) -> ())
Related
I have a view which contains some few texts, button and camera view inside a scroll view
VStack {
ZStack{
ScrollView{
HStack(alignment: .top) {
LogoMainView()
}
.frame(maxWidth: .infinity, maxHeight: 44, alignment: .leading)
.padding(.horizontal,5)
VStack{
HeaderText(title:"Some header")
ParagraphText(text:"Some random text")
CameraDropDown(
toggleFront:{
self.captureSession.changeOrientation(orientation: .front)
},toggleBack: {
self.captureSession.changeOrientation(orientation: .back)
})
CameraView()
.onAppear(){
self.appDelegate.captureSession.setup()
self.appDelegate.captureSession.start()
}
.onDisappear(){
self.appDelegate.captureSession.stop()
}
.onChange(of: self.appDelegate.captureSession.orientation){
newvalue in
self.appDelegate.captureSession.orientation = newvalue
self.appDelegate.captureSession.setup()
self.appDelegate.captureSession.start()
}
}
.padding(.horizontal,10)
}
}
}
.clipped()
}
Whenever the camera starts on clicking CameraDropDown it should change the orientation but expanding then choosing the desired orientation but when the CameraView() is running i.e (the camera is showing) it just does not respond to touches but when i switch of the CameraView() it the responds to the touchess and orientation changes.
What i have tried is to profile for hangs but i could not get a hang.
here is my cameraclass
//
// CaptureSession.swift
import Foundation
import AVFoundation
import SwiftUI
class CaptureSession: NSObject, ObservableObject {
#Published var sampleBuffer: CMSampleBuffer?
#Published var orientation: AVCaptureDevice.Position = .back
var captureSession: AVCaptureSession?
func setup() {
var allowedAccess = false
let blocker = DispatchGroup()
blocker.enter()
AVCaptureDevice.requestAccess(for: .video) { flag in
allowedAccess = flag
blocker.leave()
}
blocker.wait()
if !allowedAccess { return }
if !allowedAccess {
print("Camera access is not allowed.")
return
}
let session = AVCaptureSession()
session.beginConfiguration()
let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: orientation)
guard videoDevice != nil, let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice!), session.canAddInput(videoDeviceInput) else {
print("Unable to detect camera.")
return
}
session.addInput(videoDeviceInput)
session.commitConfiguration()
let videoOutput = AVCaptureVideoDataOutput()
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "SampleBuffer"))
if (session.canAddOutput(videoOutput)) {
session.addOutput(videoOutput)
}
self.captureSession = session
}
func start() {
guard let captureSession = self.captureSession else {
return
}
if (!captureSession.isRunning) {
DispatchQueue.global(qos: .default).async {
captureSession.startRunning()
}
}
}
func stop() {
guard let captureSession = self.captureSession else {
return
}
if (captureSession.isRunning) {
captureSession.stopRunning()
}
}
func changeOrientation(
orientation:AVCaptureDevice.Position) {
guard let captureSession = self.captureSession else {
return
}
if(captureSession.isRunning){
DispatchQueue.main.async {
self.orientation = orientation
self.setup()
self.start()
}
}else{
DispatchQueue.main.async {
self.orientation = orientation
self.setup()
self.start()
}
}
}
}
extension CaptureSession: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
DispatchQueue.main.async {
self.sampleBuffer = sampleBuffer
}
}
}
how can I make the camera run and also respond to the touches for the other views that I have or what am I doing wrong here? Please help.
I am creating an object recognition app that takes frames from the camera and outputs a description of the image into a text view on the screen. I can print the output to the console, but cannot get the view to update with the text using the ObservableObject protocol. I have looked for solutions but none have seemed to work.
Camera View:
struct Analysis {
var description: String = "No description"
var confidence: Double = 0.0
}
final class CameraView: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate, ObservableObject {
var captureSession: AVCaptureSession!
var previewLayer: AVCaptureVideoPreviewLayer!
let dataOutput = AVCaptureVideoDataOutput()
var frameCounter = 0
#Published var analysis = Analysis()
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(CameraView.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
captureSession = AVCaptureSession()
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }
let videoInput: AVCaptureDeviceInput
do {
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
} catch {
return
}
if (captureSession.canAddInput(videoInput)) {
captureSession.addInput(videoInput)
} else {
failed()
return
}
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)
captureSession.startRunning()
dataOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))
captureSession.addOutput(dataOutput)
}
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
frameCounter += 1
if frameCounter == 15 {
frameCounter = 0
guard let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {return}
guard let model = try? VNCoreMLModel(for: Resnet50().model) else {return}
let request = VNCoreMLRequest(model: model) { finishedReq, err in
guard let results = finishedReq.results as? [VNClassificationObservation] else {return}
guard let firstObservation = results.first else {return}
self.analysis = Analysis(description: firstObservation.identifier, confidence: Double(firstObservation.confidence))
}
try? VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:]).perform([request])
print(self.analysis)
}
}
extension CameraView: UIViewControllerRepresentable {
public typealias UIViewControllerType = CameraView
func makeUIViewController(context: UIViewControllerRepresentableContext<CameraView>) -> CameraView {
return CameraView()
}
func updateUIViewController(_ uiViewController: CameraView, context: UIViewControllerRepresentableContext<CameraView>) {
}
}
Content View:
struct ContentView: View {
#ObservedObject var camera = CameraView()
var body: some View {
ZStack {
camera
.ignoresSafeArea()
VStack {
Spacer()
ZStack {
Rectangle()
.frame(height: 75)
.background(.regularMaterial)
.cornerRadius(20)
.padding()
Text(camera.analysis.description)
.font(.largeTitle)
}
}
}
}
}
Please let me know if you need more information.
So I realize there are several questions about this but I feel like I've reviewed them all and still haven't figured out what I did wrong or different. I call this class from a function that I know happens after viewDidLoad. What I am trying to do is be able to take a video stream from a given AVCaptureDevice or camera id and pass this into a WebView as a series of evaluateJavascript calls, I may need to optimize this later but I'm having trouble just getting captureOutput to be called. Certainly, I don't need a preview to be able to capture the output do it? I've confirmed the permissions are correct and the code reaches the point where the sample buffer delegate is being set on the capture session. Any ideas?
class CameraPlugin:
NSObject,
AVCaptureVideoDataOutputSampleBufferDelegate,
AVCaptureMetadataOutputObjectsDelegate,
AVCapturePhotoCaptureDelegate {
private var capturePhotoCompletion: ((Result<UIImage, Error>) -> ())?
private var scanBarcodeCompletion: ((Result<String, Error>) -> ())?
let captureSession = AVCaptureSession()
private var videoSampleListener: VideoSampleListener?
func startStreamingCamera(cameraId: String?, camera: AVCaptureDevice?, listener: VideoSampleListener) {
self.videoSampleListener = listener
var inputCam = camera
if (cameraId != nil) {
inputCam = self.retrieveVideoCaptureDeviceFromId(id: cameraId!)
if (inputCam == nil) {
return
}
} else if (inputCam == nil) {
return
}
self.haveCaptureDeviceAccess(type: .video) { granted in
if granted {
do {
let captureDeviceInput = try AVCaptureDeviceInput(device: inputCam!)
let captureDeviceOutput = AVCaptureVideoDataOutput()
guard
self.captureSession.canAddInput(captureDeviceInput),
self.captureSession.canAddOutput(captureDeviceOutput)
else {
return
}
self.captureSession.addInput(captureDeviceInput)
self.captureSession.addOutput(captureDeviceOutput)
captureDeviceOutput.setSampleBufferDelegate(self, queue: .global())
} catch {
}
} else {
}
}
}
/*
* Converts captured video frame to a jpeg image
*/
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
return
}
let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
guard let cgImage = CIContext().createCGImage(ciImage, from: ciImage.extent) else {
return
}
let image = UIImage(cgImage: cgImage, scale: 1.0, orientation: .right)
guard let imageData = image.jpegData(compressionQuality: 0.7)?.base64EncodedString() else {
return
}
if (videoSampleListener != nil) {
videoSampleListener!.receivedVideoSample(imageData: imageData)
}
}
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
}
private func configurePhotoSettings() -> AVCapturePhotoSettings {
let settings = AVCapturePhotoSettings()
settings.isHighResolutionPhotoEnabled = true
return settings
}
private func retrieveBarcodeMetadataObjectTypes() -> [AVMetadataObject.ObjectType] {
return [
AVMetadataObject.ObjectType.upce,
AVMetadataObject.ObjectType.code39,
AVMetadataObject.ObjectType.code39Mod43,
AVMetadataObject.ObjectType.ean13,
AVMetadataObject.ObjectType.ean8,
AVMetadataObject.ObjectType.code93,
AVMetadataObject.ObjectType.code128,
AVMetadataObject.ObjectType.pdf417,
AVMetadataObject.ObjectType.qr,
AVMetadataObject.ObjectType.aztec,
AVMetadataObject.ObjectType.interleaved2of5,
AVMetadataObject.ObjectType.itf14,
AVMetadataObject.ObjectType.dataMatrix
]
}
private func haveCaptureDeviceAccess(type: AVMediaType, completion: #escaping (Bool) -> ()) {
switch AVCaptureDevice.authorizationStatus(for: type) {
case .denied:
completion(false)
case .notDetermined:
AVCaptureDevice.requestAccess(for: type) { granted in
completion(granted)
}
default:
completion(true)
}
}
func retrieveVideoCaptureDeviceFromId(id: String) -> AVCaptureDevice? {
return self.retrieveAvailableVideoCaptureDevices().first(where: { device in device.uniqueID == id })
}
func retrieveAvailableVideoCaptureDevices() -> [AVCaptureDevice] {
let discoverySession = AVCaptureDevice.DiscoverySession(
deviceTypes: self.retrievePlatformDeviceTypes(),
mediaType: .video,
position: .unspecified
)
return discoverySession.devices
}
private func retrievePlatformDeviceTypes() -> [AVCaptureDevice.DeviceType] {
var deviceTypes: [AVCaptureDevice.DeviceType] = [
.builtInDualCamera,
.builtInWideAngleCamera,
.builtInTelephotoCamera
]
if #available(iOS 11.1, *) {
deviceTypes += [
.builtInTrueDepthCamera
]
}
if #available(iOS 13.0, *) {
deviceTypes += [
.builtInDualWideCamera,
.builtInTripleCamera,
.builtInUltraWideCamera
]
}
return deviceTypes
}
}
The calling code:
guard let videoCaptureDevice = AVCaptureDevice.default(for: AVMediaType.video) else {
return
}
let camPlugin = CameraPlugin()
camPlugin.startStreamingCamera(cameraId: nil, camera: videoCaptureDevice, listener: self)
Update: There is actually nothing wrong with the code here. The issue I was having was that the instance of the class that is described above was being garbage collected.
As in the title, I'm trying to retrieve the CVPixelBuffer for a captured photo from output of the method:
AVCapturePhotoCaptureDelegate.photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?)
The photo parameter's pixelBuffer is nil in the delegate method call and I'd like to use it for some low level image manipulation.
I've been mostly following the sample code which can be found at:
https://developer.apple.com/library/content/samplecode/AVCam/Introduction/Intro.html
and the AVFoundation documentation.
Since the AVFoundation session configuration is kinda lengthy and might provide some answers, I'll just paste the whole object that handles it, which should contain all of the related code:
protocol CameraServiceDelegate: class {
func cameraServiceDidCapturePhoto(withBuffer buffer: CVPixelBuffer)
func cameraServiceEncounteredError(_ error: Error?)
}
final class CameraService: NSObject {
struct BufferRetrievalFailure: Error {}
weak var delegate: CameraServiceDelegate?
private let session = AVCaptureSession()
private var discoverySession = AVCaptureDevice.DiscoverySession(
deviceTypes: [.builtInDualCamera, .builtInWideAngleCamera],
mediaType: .video,
position: .back
)
private var deviceInput: AVCaptureDeviceInput!
private let photoOutput = AVCapturePhotoOutput()
private let sessionQueue = DispatchQueue(label: "av-capture-session.serial.queue")
private var captureDevice: AVCaptureDevice? {
return .default(.builtInDualCamera, for: .video, position: .back)
?? .default(.builtInWideAngleCamera, for: .video, position: .back)
?? .default(.builtInWideAngleCamera, for: .video, position: .front)
}
func setup(with layer: AVCaptureVideoPreviewLayer) {
layer.session = session
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
break
case .notDetermined:
requestVideoAuthorization()
default:
assertionFailure("Just enable video, this is not a real app.")
}
sessionQueue.async { [weak self] in
self?.setupAVSession(with: layer)
}
}
func resume() {
sessionQueue.async { [weak session] in
session?.startRunning()
}
}
func suspend() {
sessionQueue.async { [weak session] in
session?.stopRunning()
}
}
func capturePhoto() {
sessionQueue.async { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.photoOutput.capturePhoto(with: strongSelf.capturePhotoSettings(), delegate: strongSelf)
}
}
private func requestVideoAuthorization() {
sessionQueue.suspend()
AVCaptureDevice.requestAccess(for: .video) { [weak sessionQueue] isAuthorized in
guard isAuthorized else {
assertionFailure("Just enable video, this is not a real app.")
return
}
sessionQueue?.resume()
}
}
private func setupAVSession(with layer: AVCaptureVideoPreviewLayer) {
session.beginConfiguration()
session.sessionPreset = .photo
setupVideoInput()
setupVideoPreviewViewLayer(with: layer)
setupPhotoOutput()
session.commitConfiguration()
}
private func setupVideoInput() {
guard let videoDevice = captureDevice,
let deviceInput = try? AVCaptureDeviceInput(device: videoDevice),
session.canAddInput(deviceInput) else {
fatalError("Could not retrieve suitable capture device or configure video device input.")
}
self.deviceInput = deviceInput
session.addInput(deviceInput)
}
private func setupVideoPreviewViewLayer(with layer: AVCaptureVideoPreviewLayer) {
DispatchQueue.main.async {
let statusBarOrientation = UIApplication.shared.statusBarOrientation
layer.connection?.videoOrientation =
statusBarOrientation != .unknown
? AVCaptureVideoOrientation(rawValue: statusBarOrientation.rawValue)!
: .portrait
}
}
private func setupPhotoOutput() {
guard session.canAddOutput(photoOutput) else {
fatalError("Could not configure photo output.")
}
session.addOutput(photoOutput)
photoOutput.isHighResolutionCaptureEnabled = true
photoOutput.isLivePhotoCaptureEnabled = false
photoOutput.isDepthDataDeliveryEnabled = photoOutput.isDepthDataDeliverySupported
}
private func capturePhotoSettings() -> AVCapturePhotoSettings {
let settings: AVCapturePhotoSettings
if photoOutput.availablePhotoCodecTypes.contains(.hevc) {
settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.hevc])
} else {
settings = AVCapturePhotoSettings()
}
settings.isHighResolutionPhotoEnabled = true
settings.isDepthDataDeliveryEnabled = photoOutput.isDepthDataDeliveryEnabled
return settings
}
}
extension CameraService: AVCapturePhotoCaptureDelegate {
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
guard error == nil else {
delegate?.cameraServiceEncounteredError(error)
return
}
guard let buffer = photo.pixelBuffer else {
delegate?.cameraServiceEncounteredError(BufferRetrievalFailure())
return
}
delegate?.cameraServiceDidCapturePhoto(withBuffer: buffer)
}
}
I don't have a code sample for you because I'm working in Xamarin, but you need to set the previewPhotoFormat on the AVCapturePhotoSettings object used when creating the capture. An example I found online:
var settings = AVCapturePhotoSettings()
let previewPixelType = settings.availablePreviewPhotoPixelFormatTypes.first!
let previewFormat = [
kCVPixelBufferPixelFormatTypeKey as String: previewPixelType,
kCVPixelBufferWidthKey as String: self.capturedButton.frame.width,
kCVPixelBufferHeightKey as String: self.capturedButton.frame.height
] as [String : Any]
settings.previewPhotoFormat = previewFormat
Personally I inspect the availablePreviewPhotoPixelFormatTypes to see if the format that I require for my analysis (kCVPixelFormatType_32BGRA) is even in there. I haven't encountered a device without it so far.
Using Xcode 9 Beta for iOS 11:
I've followed a walkthrough on how to extract frames from an AVCaptureSession, but have not been able to get the capture to appear. While I have included the camera permissions in the info.plist file, the app seems to stall after opening and I get the following errors:
[App Name] does not have sandbox access for frZQaeyWLUvLjeuEK43hmg and IS NOT appropriately entitled
[MC] System group container for systemgroup.com.apple.configurationprofiles path is /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
[MC] Reading from public effective user settings.
Here is the code for FrameExtractor.swift for reference:
import UIKit
import AVFoundation
protocol FrameExtractorDelegate: class {
func captured(image: UIImage)
}
class FrameExtractor: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
private let position = AVCaptureDevice.Position.front
private let quality = AVCaptureSession.Preset.medium
private var permissionGranted = false
private let sessionQueue = DispatchQueue(label: "session queue")
private let captureSession = AVCaptureSession()
private let context = CIContext()
weak var delegate: FrameExtractorDelegate?
override init() {
super.init()
checkPermission()
sessionQueue.async { [unowned self] in
self.configureSession()
self.captureSession.startRunning()
}
}
// MARK: AVSession configuration
private func checkPermission() {
switch AVCaptureDevice.authorizationStatus(for: AVMediaType.video) {
case .authorized:
permissionGranted = true
case .notDetermined:
requestPermission()
default:
permissionGranted = false
}
}
private func requestPermission() {
sessionQueue.suspend()
AVCaptureDevice.requestAccess(for: AVMediaType.video) { [unowned self] granted in
self.permissionGranted = granted
self.sessionQueue.resume()
}
}
private func configureSession() {
guard permissionGranted else { return }
captureSession.sessionPreset = quality
guard let captureDevice = selectCaptureDevice() else { return }
guard let captureDeviceInput = try? AVCaptureDeviceInput(device: captureDevice) else { return }
guard captureSession.canAddInput(captureDeviceInput) else { return }
captureSession.addInput(captureDeviceInput)
let videoOutput = AVCaptureVideoDataOutput()
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "sample buffer"))
guard captureSession.canAddOutput(videoOutput) else { return }
captureSession.addOutput(videoOutput)
guard let connection = videoOutput.connection(with: AVFoundation.AVMediaType.video) else { return }
guard connection.isVideoOrientationSupported else { return }
guard connection.isVideoMirroringSupported else { return }
connection.videoOrientation = .portrait
connection.isVideoMirrored = position == .front
}
private func selectCaptureDevice() -> AVCaptureDevice? {
return AVCaptureDevice.default(for: AVMediaType.video)
// return AVCaptureDevice.devices().filter {
// ($0 as AnyObject).hasMediaType(AVMediaType.video) &&
// ($0 as AnyObject).position == position
// }.first
}
// MARK: Sample buffer to UIImage conversion
private func imageFromSampleBuffer(sampleBuffer: CMSampleBuffer) -> UIImage? {
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return nil }
let ciImage = CIImage(cvPixelBuffer: imageBuffer)
guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { return nil }
return UIImage(cgImage: cgImage)
}
// MARK: AVCaptureVideoDataOutputSampleBufferDelegate
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) {
print("Got a Frame!")
guard let uiImage = imageFromSampleBuffer(sampleBuffer: sampleBuffer) else { return }
DispatchQueue.main.async { [unowned self] in
self.delegate?.captured(image: uiImage)
}
}
}
And for ViewController.swift:
import UIKit
class ViewController: UIViewController, FrameExtractorDelegate{
#IBOutlet var imageView: UIImageView!
var frameExtractor: FrameExtractor!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
frameExtractor = FrameExtractor()
frameExtractor.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func captured(image: UIImage) {
imageView.image = image
}
}`
The issue is in a different function call in captureOutput. This is the new function call in iOS 11 for captureOutput in AVCaptureVideoDataOutputSampleBufferDelegate:
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let uiImage = imageFromSampleBuffer(sampleBuffer: sampleBuffer) else { return }
DispatchQueue.main.async { [unowned self] in
self.delegate?.captured(image: uiImage)
}
}
Notice the change between "didOutput sampleBuffer:" and "didOutputSampleBuffer sampleBuffer:"