Comment créer une caméra d'animation Stop Motion avec AVFoundation dans iOS

La construction d'une caméra est l'une des tâches courantes du développement d'une application iOS pour réseaux sociaux. Dans cet article, nous allons créer une application avec une caméra pour créer l'effet d'animation Stop motion. L’effet stop motion est obtenu en lisant une série d’images sous forme de séquence rapide créant une illusion de mouvement.

Stop-Motion créé en utilisant AVFoundation - Fin du tutoriel, vous devriez avoir une application qui fait ça :)
Tout d'abord, la création d'une application iOS avec les fonctionnalités de l'appareil photo peut être réalisée de deux manières différentes. Il utilise soit un UIImagePickerController, soit via le framework AVFoundation.

Qu'est-ce que AVFoundation?

AVFoundation est un framework iOS utilisé pour intégrer des fonctionnalités audiovisuelles puissantes dans votre application. À l'aide de cette structure, vous pouvez manipuler la capture, le traitement, l'édition, l'importation et l'exportation des ressources audio et vidéo.

UIImagePickerController vs AVFoundation:

Avec UIImagePickerController, nous pouvons effectuer toutes les tâches de base telles que la capture, le basculement du flash, le changement de caméra, la modification du focus et des expositions. En outre, il vous permet d'accéder à la photothèque pour stocker et partager des images. Il est plus facile à mettre en œuvre par rapport à AVFoundation.

Cependant, AVFoundation offre un contrôle total sur les paramètres de l'appareil photo et la manipulation des supports. Il vous permet d'effectuer toutes les tâches de base en tant que UIImagePickerController. Vous pouvez également traiter les données brutes de capture, de lecture, de création d'images miniatures / fixes à partir de vidéo, de modification et de modification de paramètres tels que la mise au point, l'exposition, etc.

Comment utiliser AVFoundation?

AVFoundation a 5 classes principales:

  • AVCaptureDevice: représente un périphérique de capture physique tel que la caméra et le microphone. Pour chaque iPhone, il existe un microphone (entrée audio) et deux caméras (avant et arrière pour une entrée visuelle). Vous pouvez configurer les paramètres d'un périphérique d'entrée à l'aide de cette opération avant de les transmettre à la session de capture.
  • AVCaptureDeviceInput: fournit le support d'un périphérique de capture à une session de capture.
  • AVCaptureOutput: représente la sortie d'un périphérique de capture. La sortie de la capture peut être l'une des suivantes: AVCaptureMovieFileOutput, AVCaptureVideoDataOutput, AVCaptureAudioDataOutput ou AVCapturePhotoOutput. AVCapturePhotoOutput représente une sortie d'image fixe. Nous pouvons configurer les paramètres de sortie tels que les paramètres prédéfinis, le format des données et la représentation des données brutes pour AVCapturePhotoOutput.
  • AVCaptureSession: agit comme un coordinateur entre les entrées et les sorties AV. Vous initialisez une session de capture, ajoutez l'entrée et la sortie de la capture à la session. Ensuite, vous lancez la session en cours pour créer le flux de données entre l’entrée et la sortie.
  • AVCaptureVideoPreviewLayer: Outre ce qui précède, pour fournir à l'utilisateur un aperçu en temps réel de ce qui est capturé ou enregistré, ceci est utilisé.

Maintenant, avec ces bases, commençons à construire l'application:

Afin de réaliser l'effet Stop Motion, nous allons permettre à l'utilisateur de prendre une série de photos et de créer un fichier GIF à l'aide de la structure AVFoundation. L'application nécessitera deux classes UIViewController et une classe pour résumer toutes les fonctionnalités de la caméra que nous utiliserons:

  • ViewController - Le contrôleur de vue initial par défaut qui est généré lorsque vous créez une nouvelle application à vue unique. Nous allons utiliser ceci pour montrer la caméra en direct avec des boutons pour modifier les paramètres de la caméra, prendre une photo et créer un média en stop motion.
  • PreviewViewController: affiche l'aperçu du mouvement arrêté avec une option permettant de l'enregistrer dans une bibliothèque de photos.
  • CameraSetup - Une abstraction de toutes les fonctionnalités de la caméra que nous utilisons. Dans cette classe, nous utiliserons AVFoundation Framework. La classe CameraViewController contiendra une instance de CameraSetup.

TL: DR Vous pouvez trouver le code de travail dans mon référentiel git.

  1. Commençons par installer le story-board. Créez un projet pour une application à vue unique. Dans le ViewController initial, ajoutez simplement une autre vue et définissez ses bords sur les bords de la vue d'ensemble. Ajoutez 4 boutons au total, comme indiqué ci-dessous, pour: - changer d'appareil photo, activer le flash, capturer et un bouton Terminé pour créer un GIF en stop motion à partir du jeu d'images capturées.

2. Créez un nouveau fichier de classe tactile cacao qui est une sous-classe de UIViewController. Nommez-le «PreviewViewController», car cela affichera l'aperçu final du GIF créé en stop motion.

3. Ajoutez un autre contrôleur de vue au storyboard. Incluez une vue d'image et définissez ses bords sur ceux de superview. Ajoutez deux boutons ici - l’un pour enregistrer le GIF dans une photothèque et l’autre pour ignorer sans enregistrer. Dans son inspecteur d'identité, spécifiez la classe en tant que PreviewViewController.

4. Créez maintenant une séquence du ViewController vers le PreviewViewController et dans son inspecteur d'attributs, définissez l'identifiant de la séquence sur «showPreview». Nous utiliserons cela dans le code pour effectuer la transition du premier au deuxième contrôleur de vue.

5. Créez une nouvelle classe swift pour «CameraSetup» dans laquelle nous allons implémenter AVFoundation. Et nous initialiserions l'objet class dans ViewController pour effectuer des actions de caméra.

Concentrons-nous sur la classe CameraSetup où nous utiliserons AVFoundation.

importer AVFoundation
Classe CameraSetup {
....
}

Dans le fichier de classe, commençons par déclarer la liste des variables dont nous aurions besoin.

// la session de capture de caméra est initialisée
var captureSession = AVCaptureSession ()
// Liste du périphérique de capture nécessaire pour prendre une image fixe
var frontCam: AVCaptureDevice?
var rearCam: AVCaptureDevice?
var currentCam: AVCaptureDevice?
// entrée et sortie pour la capture
var captureInput: AVCaptureDeviceInput?
var captureOutput: AVCapturePhotoOutput?
// couche d'aperçu de la caméra
var previewLayer: AVCaptureVideoPreviewLayer?

Créons maintenant une fonction pour configurer et exécuter la session de capture. Commençons par CaptureDevice

func captureDevice () {
let discoverySession = AVCaptureDevice.DiscoverySession (type d'appareil: [.builtInWideAngleCamera], type de média: AVMediaType.video, position: .unspecified)
        pour d in discoverySession.devices {
            si d.position == .front {
                frontCam = d
            }
            sinon si d.position == .back {
                rearCam = d
                faire{
                    Essayez rearCam? .lockForConfiguration ()
                    rearCam? .focusMode = .autoFocus
                    rearCam? .exposureMode = .autoExpose
                    rearCam? .unlockForConfiguration ()
                }
                catch let error {
                    print (erreur)
                }
            }
        }
    }

L’utilisation de la requête DiscoverySession nous fournit la liste des périphériques d’entrée physiques correspondant aux critères spécifiés. Ici, nous voulons une liste de périphériques avec une caméra grand angle intégrée et pouvant prendre en charge les médias visuels. Ensuite, nous configurons l’appareil sur frontCam si sa position est à l’avant et il en va de même pour l’arrière.

Avec rearCam, j'ai configuré son mode de mise au point et son exposition. Voici quelques fonctionnalités pouvant être configurées à l'aide d'AVFoundation lors de la configuration d'une caméra pour la capture.

Nous avons les cartes frontCam et rearCam que nous pourrions ajouter comme entrées de capture. Permet de créer une fonction pour configurer captureInput, pour ajouter des entrées à la session.

func configureCaptureInput () {
        currentCam = rearCam!
        faire{
            captureInput = essayer AVCaptureDeviceInput (device: currentCam!)
            si captureSession.canAddInput (captureInput!) {
                captureSession.addInput (captureInput!)
            }
        }
        catch let error {
            print (erreur)
        }
    }

Ici, nous vérifions si l’entrée peut être ajoutée à la session, si elle est vraie, nous l’ajoutons. De même, nous devons configurer et ajouter CaptureOutput à la session. Ajoutons donc la fonction suivante à la classe,

func configureCaptureOutput () {
captureOutput = AVCapturePhotoOutput ()
        captureOutput! .setPreparedPhotoSettingsArray ([AVCapturePhotoSettings (format: [AVVideoCodecKey: AVVideoCodecType.jpeg]),] (complétant le gestionnaire: nil)
        si captureSession.canAddOutput (captureOutput!) {
            captureSession.addOutput (captureOutput!)
        }
        captureSession.startRunning ()
}

setPreparedPhotoSettingsArray définit le paramètre de sortie photo de notre désir. Une fois que captureOutput est ajouté à la session, il est temps de démarrer la session. Pour cela, nous appelons startRunning ().

Enfin, ce qu'il reste des cinq classes est AVVideoPreviewLayer. Continuons et ajoutons cette dernière partie.

func configurePreviewLayer (vue: UIView) {
  previewLayer = AVCaptureVideoPreviewLayer (session: captureSession)
  
  previewLayer? .videoGravity = AVLayerVideoGravity.resizeAspectFill
  previewLayer? .connection? .videoOrientation = .portrait
        
  view.layer.insertSublayer (previewLayer !, à: 0)
  previewLayer? .frame = view.frame
 }

À cette fonction, nous passons l'UIView que nous avons créé dans le premier ViewController. AVCaptureVideoPreviewLayer est une sous-classe de CALayer (Core Animation) et fonctionne simultanément avec la captureSession donnée.

6. Accédez au ViewController, connectez la vue, le bouton et ses actions au contrôleur de vue à partir du story-board. Cela ressemblerait à quelque chose comme ça,

Ajoutez une fonction appelée initialize pour appeler toutes les fonctions de configuration à partir de CameraSetup.

var cameraSetup: CameraSetup!
...
func initialize () {
        cameraSetup = CameraSetup ()
        cameraSetup.captureDevice ()
        cameraSetup.configureCaptureInput ()
        cameraSetup.configureCaptureOutput ()
        cameraSetup.configurePreviewLayer (vue: camView)
    }

Remarque: Vous ne pouvez pas exécuter ceci dans un simulateur. Exécutez l'application sur votre iPhone. Vous devriez pouvoir voir l'aperçu.

Notez que lorsque nous cliquons sur le bouton de capture, rien ne se passe. C'est parce que nous n'avons pas encore configuré cette fonctionnalité. Alors ajoutons cela maintenant dans le contrôleur de vue.

Capturer une image est une action asynchrone. Cela signifie que vous devez transmettre une fonction de rappel à AVFoundation qui sera appelée une fois l'image capturée.
var previewImage = [UIImage] ()
...
@IBAction func captureAction (_ expéditeur: N'importe lequel) {
        cameraSetup.captureImage {(image, erreur) dans
            garde laisser image = image else {
                print (error ?? "Erreur de capture d'image")
                revenir
            }
            self.previewImage.append (image)
        }
}

Nous créons actuellement un tableau UIImage pour stocker toutes les images capturées afin de créer le stop-motion. Dans captureAction, nous appelons la fonction captureImage de CameraSetup avec une fermeture en tant que paramètre. L'image renvoyée dans cette fermeture est ensuite enregistrée dans le tableau.

Dans la classe CameraSetup, ajoutez la fonction suivante

var photoCaptureCompletionBlock: ((UIImage ?, Error?) -> Void)?
...
func captureImage (complétion: @escaping (UIImage ?, Error?) -> Void) {
        laissez les paramètres = AVCapturePhotoSettings ()
        settings.flashMode = self.flashMode
        
        self.captureOutput? .capturePhoto (avec: settings, delegate: self as AVCapturePhotoCaptureDelegate)
        self.photoCaptureCompletionBlock = complétion
    }

photoCaptureCompletionBlock est le nom de la fermeture définie dans notre captureAction.

capturePhoto est utilisé pour capturer une photographie immobile avec les paramètres spécifiés pour la photo, tels que l'option flash, le format des données, etc. Cet appel nécessite l'implémentation de AVCapturePhotoCaptureDelegate. Notez que nous nous sommes créés en tant que délégué et ajoutons donc une fonction pour suivre le protocole du délégué.

public func photoOutput (_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, erreur: erreur?) {
        si x = erreur {
            self.photoCaptureCompletionBlock? (nil, x)
        }
        sinon si laissez data = photo.fileDataRepresentation (), laissez image = UIImage (data: data) {
            self.photoCaptureCompletionBlock? (image, rien)
        }
    }

DidFinishProcessingPhoto nous fournit une image finale traitée que nous allons stocker dans notre tableau.

7. Le bouton Fonctionnalité pour Fait du ViewController inclura la préparation et l'exécution de la séquence. Rappelez-vous la séquence “showPreview” que nous avons créée et qui est appelée ici lorsque le bouton Terminé est cliqué. La définition du bouton Terminé,

@IBAction func doneAction (_ expéditeur: N'importe lequel) {
    self.performSegue (withIdentifier: "showPreview", expéditeur: self)
    }
override func prepare (pour la division: UIStoryboardSegue, expéditeur: Any?) {
   si laisser destination = segue.destination comme? PreviewViewController {
            destination.pImg = self.previewImage
        }
    }

8. Ouvrez votre PreviewViewController, connectez UIImageView au contrôleur de vue. Créez un tableau UIImage appelé «pImg» et une minuterie. Dans le viewDidLoad (), ajoutez ce qui suit.

remplacer func viewDidLoad () {
  super.viewDidLoad ()
  preview.image = pImg [0]
  timer = Timer.scheduledTimer (timeInterval: 0.5, target: self, sélecteur: #selector (self.display), userInfo: nil, répète: true)
    }
    
    @objc func display () {
        si! (pImg.count == compteur) {
            preview.image = pImg [compteur]
            compteur + = 1
        }
        autre {
            compteur = 0
        }
    }

Une minuterie simple est créée pour exécuter l'aperçu des images définies en boucle à l'aide de l'affichage ().

On a presque fini. Ajoutons les blocs d'action pour le bouton de sauvegarde et de fermeture. Ajoutez le closeAction () où nous allons invalider le timer et ignorer le PreviewViewController.

@IBAction func closeAction (_ expéditeur: N'importe lequel) {
        timer.invalidate ()
        licencier (animé: vrai, achèvement: nul)
    }

Maintenant, pour enregistrer le jeu d’images au format GIF dans une photothèque, procédez comme suit:

@IBAction func saveAction (_ expéditeur: N'importe lequel) {
        createGIF (images: pImg)
        licencier (animé: vrai, achèvement: nul)
    }

La fonction createGIF crée un fichier GIF à l'aide de kCGImagePropertyGIFDictionary, puis crée une URL contenant les données de l'image. A partir de cette URL, utilisez PHPhotoLibrary pour créer et ajouter un actif à la photothèque. (pour référence)

importer des photos
...
func createGIF (images: [UIImage]) {
        let fileProperties: CFDictionary = [kCGImagePropertyGIFDictionary en tant que chaîne: [kCGImagePropertyGIFLoopCount en tant que chaîne: 0]] en tant que CFDictionary
        let frameProperties: CFDictionary = [kCGImagePropertyGIFDictionary en tant que chaîne: [kCGImagePropertyGIFUnclampedDelayTime en tant que chaîne: 0.5]] en tant que CFDictionary
        
        laisser documentsDirectoryURL: URL? = essayer? FileManager.default.url (pour: .documentDirectory, dans: .userDomainMask, appropriéPour: nil, créer: true)
        laisser fileURL: URL? = documentsDirectoryURL? .appendingPathComponent ("animated.gif")
        si url = fileURL en tant que CFURL? {
            si laissez destination = CGImageDestinationCreateWithURL (url, kUTTypeGIF, images.count, nil) {
  CGImageDestinationSetProperties (destination, fileProperties)
pour l'image en images {
 si let cgImage = image.cgImage {
  CGImageDestinationAddImage (destination, cgImage, frameProperties)
 }
}
if! CGImageDestinationFinalize (destination) {
   print ("Impossible de finaliser la destination de l'image")
}
                print ("Url = \ (fileURL!)")
}
}
 
// Demande de créer un actif à partir de l'image et de l'enregistrer dans
// Galerie de photos.
PHPhotoLibrary.shared (). PerformChanges ({PHAssetChangeRequest.creationRequestForAssetFromImage (atFileURL: fileURL!)
        })
    }

Et nous avons fini.

Il ne reste plus maintenant que les fonctionnalités optionnelles d'appareil photo et de flash. Pour le basculement de caméra, si la session dispose de rearCam, nous supprimons toutes les entrées de la session et ajoutons frontCam en tant que nouvelle captureInput et transformons currentCam en frontCam, et inversement.

func toggleCam () {
        captureSession.beginConfiguration ()
        laisser newCam = (currentCam? .position == .front)? rearCam: frontCam
        
        pour l'entrée dans captureSession.inputs {
            captureSession.removeInput (entrée en tant que! AVCaptureDeviceInput)
        }
        
        currentCam = newCam
        faire{
            captureInput = essayer AVCaptureDeviceInput (device: currentCam!)
            si captureSession.canAddInput (captureInput!) {
                captureSession.addInput (captureInput!)
            }
        }
        catch let error {
            print (erreur)
        }
        
        captureSession.commitConfiguration ()
    }

Dans le contrôleur de vue pour cameraToggle, appelez this func de CameraSetup,

@IBAction func cameraToggle (_ expéditeur: N'importe lequel) {
        cameraSetup.toggleCam ()
    }

De même pour le basculement rapide, changez simplement la valeur de la variable flashMode.

@IBAction func flashToggle (_ expéditeur: N'importe lequel) {
 si cameraSetup.flashMode == .off {
     flashButton.setImage (UIImage (nommé: "flash_on"), pour: .normal)
     cameraSetup.flashMode = .on
 }
 autre {
     flashButton.setImage (UIImage (nommé: "flash_off"), pour: .normal)
      cameraSetup.flashMode = .off
 }
}

Voilà, nous avons maintenant une application entièrement fonctionnelle. Lancer l'application. La sortie devrait être comme,

Le projet complet est disponible via le lien GitHub mentionné ci-dessous. Tous les commentaires et toutes les questions sont les bienvenus. Créez votre propre caméra personnalisée maintenant avec AVFoundation. Bonne codage .. :)