-
Tournez des vidéos de qualité cinéma dans votre app
Découvrez comment l'API Cinematic Video permet à votre app de capturer sans effort des vidéos dignes du cinéma. Nous verrons comment configurer une séance de capture cinématographique et présenterons les bases de la création d'une interface utilisateur de capture vidéo. Nous explorerons également des fonctionnalités cinématographiques avancées, telles que l'application d'un effet de profondeur de champ pour obtenir à la fois un suivi de mise au point et un effet de rack focus.
Chapitres
- 0:00 - Introduction
- 0:33 - Vidéo cinématographique
- 3:44 - Créez une expérience de capture cinématographique exceptionnelle
Ressources
Vidéos connexes
WWDC25
WWDC24
WWDC23
-
Rechercher dans cette vidéo…
-
-
4:26 - Select a video device
// Select a video device let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualWideCamera], mediaType: .video, position: .back) guard let camera = deviceDiscoverySession.devices.first else { print("Failed to find the capture device") return } -
5:07 - Select a format that supports Cinematic Video capture
// Select a format that supports Cinematic Video capture for format in camera.formats { if format.isCinematicVideoCaptureSupported { try! camera.lockForConfiguration() camera.activeFormat = format camera.unlockForConfiguration() break } } -
5:51 - Select a microphone
// Select a microphone let audioDeviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes [.microphone], mediaType: .audio, position: .unspecified) guard let microphone = audioDeviceDiscoverySession.devices.first else { print("Failed to find a microphone") return } -
6:00 - Add devices to input & add inputs to the capture session & enable Cinematic Video capture
// Add devices to inputs let videoInput = try! AVCaptureDeviceInput(device: camera) guard captureSession.canAddInput(videoInput) else { print("Can't add the video input to the session") return } let audioInput = try! AVCaptureDeviceInput(device: microphone) guard captureSession.canAddInput(audioInput) else { print("Can't add the audio input to the session") return } // Add inputs to the capture session captureSession.addInput(videoInput) captureSession.addInput(audioInput) // Enable Cinematic Video capture if (videoInput.isCinematicVideoCaptureSupported) { videoInput.isCinematicVideoCaptureEnabled = true } -
6:17 - Capture spatial audio
// Configure spatial audio if audioInput.isMultichannelAudioModeSupported(.firstOrderAmbisonics) { audioInput.multichannelAudioMode = .firstOrderAmbisonics } -
// Add outputs to the session let movieFileOutput = AVCaptureMovieFileOutput() guard captureSession.canAddOutput(movieFileOutput) else { print("Can't add the movie file output to the session") return } captureSession.addOutput(movieFileOutput) // Configure video stabilization if let connection = movieFileOutput.connection(with: .video), connection.isVideoStabilizationSupported { connection.preferredVideoStabilizationMode = .cinematicExtendedEnhanced } // Add a preview layer as the view finder let previewLayer = AVCaptureVideoPreviewLayer() previewLayer.session = captureSession -
7:11 - Display the preview layer with SwiftUI
// Display the preview layer with SwiftUI struct CameraPreviewView: UIViewRepresentable { func makeUIView(context: Context) -> PreviewView { return PreviewView() } class CameraPreviewUIView: UIView { override class var layerClass: AnyClass { AVCaptureVideoPreviewLayer.self } var previewLayer: AVCaptureVideoPreviewLayer { layer as! AVCaptureVideoPreviewLayer } ... } ... } -
7:54 - Display the preview layer with SwiftUI
// Display the preview layer with SwiftUI @MainActor struct CameraView: View { var body: some View { ZStack { CameraPreviewView() CameraControlsView() } } } -
8:05 - Adjust bokeh strength with simulated aperture
// Adjust bokeh strength with simulated aperture open class AVCaptureDeviceInput : AVCaptureInput { open var simulatedAperture: Float ... } -
8:40 - Find min, max, and default simulated aperture
// Adjust bokeh strength with simulated aperture extension AVCaptureDeviceFormat { open var minSimulatedAperture: Float { get } open var maxSimulatedAperture: Float { get } open var defaultSimulatedAperture: Float { get } ... } -
9:12 - Add a metadata output
// Add a metadata output let metadataOutput = AVCaptureMetadataOutput() guard captureSession.canAddOutput(metadataOutput) else { print("Can't add the metadata output to the session") return } captureSession.addOutput(metadataOutput) metadataOutput.metadataObjectTypes = metadataOutput.requiredMetadataObjectTypesForCinematicVideoCapture metadataOutput.setMetadataObjectsDelegate(self, queue: sessionQueue) -
9:50 - Update the observed manager object
// Update the observed manager object func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { self.metadataManager.metadataObjects = metadataObjects } // Pass metadata to SwiftUI @Observable class CinematicMetadataManager { var metadataObjects: [AVMetadataObject] = [] } -
10:12 - Observe changes and update the view
// Observe changes and update the view struct FocusOverlayView : View { var body: some View { ForEach( metadataManager.metadataObjects, id:\.objectID) { metadataObject in rectangle(for: metadataObject) } } } -
10:18 - Make a rectangle for a metadata
// Make a rectangle for a metadata private func rectangle(for metadata: AVMetadataObjects) -> some View { let transformedRect = previewLayer.layerRectConverted(fromMetadataOutputRect: metadata.bounds) return Rectangle() .frame(width:transformedRect.width, height:transformedRect.height) .position( x:transformedRect.midX, y:transformedRect.midY) } -
10:53 - Focus methods
open func setCinematicVideoTrackingFocus(detectedObjectID: Int, focusMode: AVCaptureDevice.CinematicVideoFocusMode) open func setCinematicVideoTrackingFocus(at point: CGPoint, focusMode: AVCaptureDevice.CinematicVideoFocusMode) open func setCinematicVideoFixedFocus(at point: CGPoint, focusMode: AVCaptureDevice.CinematicVideoFocusMode) -
10:59 - Focus method 1 & CinematicVideoFocusMode
// Focus methods open func setCinematicVideoTrackingFocus(detectedObjectID: Int, focusMode: AVCaptureDevice.CinematicVideoFocusMode) public enum CinematicVideoFocusMode : Int, @unchecked Sendable { case none = 0 case strong = 1 case weak = 2 } extension AVMetadataObject { open var cinematicVideoFocusMode: Int32 { get } } -
12:19 - Focus method no.2
// Focus method no.2 open func setCinematicVideoTrackingFocus(at point: CGPoint, focusMode: AVCaptureDevice.CinematicVideoFocusMode) -
12:41 - Focus method no.3
// Focus method no.3 open func setCinematicVideoFixedFocus(at point: CGPoint, focusMode: AVCaptureDevice.CinematicVideoFocusMode) -
13:54 - Create the spatial tap gesture
var body: some View { let spatialTapGesture = SpatialTapGesture() .onEnded { event in Task { await camera.focusTap(at: event.location) } } ... } -
14:15 - Simulate a long press gesture with a drag gesture
@State private var pressLocation: CGPoint = .zero @State private var isPressing = false private let longPressDuration: TimeInterval = 0.3 var body: some View { ... let longPressGesture = DragGesture(minimumDistance: 0).onChanged { value in if !isPressing { isPressing = true pressLocation = value.location startLoopPressTimer() } }.onEnded { _ in isPressing = false } ... } private func startLoopPressTimer() { DispatchQueue.main.asyncAfter(deadline: .now() + longPressDuration) { if isPressing { Task { await camera.focusLongPress(at: pressLocation) } } } } -
14:36 - Create a rectangle view to receive gestures.
var body: some View { let spatialTapGesture = ... let longPressGesture = ... ZStack { ForEach( metadataManager.metadataObjects, id:\.objectID) { metadataObject in rectangle(for: metadataObject) } Rectangle() .fill(Color.clear) .contentShape(Rectangle()) .gesture(spatialTapGesture) .gesture(longPressGesture)} } } -
15:03 - Create the rectangle view
private func rectangle(for metadata: AVMetadataObject) -> some View { let transformedRect = previewLayer.layerRectConverted(fromMetadataOutputRect: metadata.bounds) var color: Color var strokeStyle: StrokeStyle switch metadata.focusMode { case .weak: color = .yellow strokeStyle = StrokeStyle(lineWidth: 2, dash: [5,4]) case .strong: color = .yellow strokeStyle = StrokeStyle(lineWidth: 2) case .none: color = .white strokeStyle = StrokeStyle(lineWidth: 2) } return Rectangle() .stroke(color, style: strokeStyle) .contentShape(Rectangle()) .frame(width: transformedRect.width, height: transformedRect.height) .position(x: transformedRect.midX, y: transformedRect.midY) } -
15:30 - Implement focusTap
func focusTap(at point:CGPoint) { try! camera.lockForConfiguration() if let metadataObject = findTappedMetadataObject(at: point) { if metadataObject.cinematicVideoFocusMode == .weak { camera.setCinematicVideoTrackingFocus(detectedObjectID: metadataObject.objectID, focusMode: .strong) } else { camera.setCinematicVideoTrackingFocus(detectedObjectID: metadataObject.objectID, focusMode: .weak) } } else { let transformedPoint = previewLayer.metadataOutputRectConverted(fromLayerRect: CGRect(origin:point, size:.zero)).origin camera.setCinematicVideoTrackingFocus(at: transformedPoint, focusMode: .weak) } camera.unlockForConfiguration() } -
15:42 - Implement findTappedMetadataObject
private func findTappedMetadataObject(at point: CGPoint) -> AVMetadataObject? { var metadataObjectToReturn: AVMetadataObject? for metadataObject in metadataObjectsArray { let layerRect = previewLayer.layerRectConverted(fromMetadataOutputRect: metadataObject.bounds) if layerRect.contains(point) { metadataObjectToReturn = metadataObject break } } return metadataObjectToReturn } -
16:01 - focusTap implementation continued
func focusTap(at point:CGPoint) { try! camera.lockForConfiguration() if let metadataObject = findTappedMetadataObject(at: point) { if metadataObject.cinematicVideoFocusMode == .weak { camera.setCinematicVideoTrackingFocus(detectedObjectID: metadataObject.objectID, focusMode: .strong) } else { camera.setCinematicVideoTrackingFocus(detectedObjectID: metadataObject.objectID, focusMode: .weak) } } else { let transformedPoint = previewLayer.metadataOutputRectConverted(fromLayerRect: CGRect(origin:point, size:.zero)).origin camera.setCinematicVideoTrackingFocus(at: transformedPoint, focusMode: .weak) } camera.unlockForConfiguration() } -
16:23 - Implement focusLongPress
func focusLongPress(at point:CGPoint) { try! camera.lockForConfiguration() let transformedPoint = previewLayer.metadataOutputRectConverted(fromLayerRect:CGRect(origin: point, size: CGSizeZero)).origin camera.setCinematicVideoFixedFocus(at: pointInMetadataOutputSpace, focusMode: .strong) camera.unlockForConfiguration() } -
17:10 - Introduce cinematicVideoCaptureSceneMonitoringStatuses
extension AVCaptureDevice { open var cinematicVideoCaptureSceneMonitoringStatuses: Set<AVCaptureSceneMonitoringStatus> { get } } extension AVCaptureSceneMonitoringStatus { public static let notEnoughLight: AVCaptureSceneMonitoringStatus } -
17:42 - KVO handler for cinematicVideoCaptureSceneMonitoringStatuses
private var observation: NSKeyValueObservation? observation = camera.observe(\.cinematicVideoCaptureSceneMonitoringStatuses, options: [.new, .old]) { _, value in if let newStatuses = value.newValue { if newStatuses.contains(.notEnoughLight) { // Update UI (e.g., "Not enough light") } else if newStatuses.count == 0 { // Back to normal. } } }
-
-
- 0:00 - Introduction
Utilisez l’API Cinematic Video pour capturer des vidéos de qualité cinéma dans vos apps. L’iPhone 13 et l’iPhone 13 Pro ont introduit le mode Cinematic, qui transforme l’iPhone en un véritable outil de cinéma.
- 0:33 - Vidéo cinématographique
La vidéo Cinematic utilise une faible profondeur de champ et un suivi de la mise au point pour guider l’attention du spectateur. Elle imite ainsi les techniques utilisées au cinéma. L’API Cinematic Video dans iOS 26 simplifie ce processus en permettant aux apps de régler et de suivre automatiquement la mise au point. Pour créer une expérience de capture Cinematic, configurez une session de capture, sélectionnez un appareil, puis activez la capture vidéo Cinematic en définissant « isCinematicVideoCaptureEnabled » sur « true » dans la classe « AVCaptureDeviceInput ». Cette opération configure la session pour qu’elle produise une vidéo Cinematic avec des données de disparité, des métadonnées et la vidéo originale, ce qui permet un montage non destructif. Vous pouvez lire ou modifier le rendu bokeh avec le framework Cinematic.
- 3:44 - Créez une expérience de capture cinématographique exceptionnelle
L’exemple commence par configurer une « AVCaptureSession » afin d’activer la capture vidéo Cinematic sur les appareils compatibles, tels que la double caméra grand-angle à l’arrière et la caméra TrueDepth à l’avant. L’exemple sélectionne un format vidéo approprié, avec « isCinematicVideoCaptureSupported » renvoyant la valeur « true », puis ajoute l’entrée audio du microphone à la session. La capture de l'audio spatial est activée pour améliorer l’expérience Cinematic. Pour en savoir plus sur l’audio spatial, regardez la session « Améliorez les capacités de création de contenu audio de votre app ». Ensuite, la stabilisation vidéo est activée afin d’améliorer l’expérience utilisateur, et la session de capture est prévisualisée à l’aide d’une vue SwiftUI. L’exemple crée ensuite une structure représentable personnalisée pour encapsuler « AVCaptureVideoPreviewLayer », ce qui permet de l’intégrer facilement dans l’interface SwiftUI. L’exemple aborde ensuite le contrôle de l’effet vidéo Cinematic, en particulier la faible profondeur de champ. En ajustant le paramètre « simulatedAperture », vous pouvez renforcer ou atténuer l’effet bokeh, ce qui vous offre un contrôle plus créatif sur la vidéo. Pour activer le contrôle manuel de la mise au point, l’exemple utilise la détection des métadonnées afin d’identifier les candidats à la mise au point, tels que les visages. Il affiche ensuite des rectangles à l’écran pour identifier ces candidats, ce qui permet aux utilisateurs de toucher l’écran et de faire la mise au point sur des sujets spécifiques. L’API Cinematic Video fournit plusieurs méthodes pour contrôler la mise au point pendant l’enregistrement vidéo. Elle génère des objets de métadonnées qui incluent des informations sur les sujets détectés dans le cadre. Le paramètre de configuration « focusMode » détermine le comportement de suivi de la vidéo Cinematic. Il existe trois cas pour cette énumération : none, strong et weak. La mise au point forte ou strong se verrouille sur un sujet et ignore les autres candidats potentiels à la mise au point. La mise au point faible ou weak permet à l’algorithme de changer automatiquement la mise au point en fonction de la scène. La mise au point inexistante ou none est principalement utilisée pour déterminer l’état de mise au point plutôt que pour le définir. L’API propose trois méthodes de mise au point : La méthode « setCinematicVideoTrackingFocus » prend en entrée l’ID d’un objet détecté et définit la mise au point sur cet objet. La méthode « setCinematicVideoTrackingFocus » utilise un point de la vue comme entrée. La vidéo Cinematic recherche ensuite un objet intéressant à cet endroit et crée un nouvel objet de métadonnées de type « salient object », sur lequel il est alors possible de se concentrer. « setCinematicVideoFixedFocus » définit une mise au point fixe sur un point spécifique de la scène en calculant la distance en interne à l’aide de signaux de profondeur. Associée à un mode de mise au point forte, cette méthode verrouille la mise au point sur un plan particulier en ignorant les autres activités de la scène. Vous pouvez utiliser une logique de mise au point personnalisée dans vos apps. Par exemple, appuyer sur un rectangle de détection peut basculer la mise au point entre les sujets, et un appui long peut définir une mise au point fixe forte. L’app différencie visuellement entre une mise au point faible, forte et inexistante pour guider l’utilisateur. De plus, l’API permet de capturer des images fixes pendant l’enregistrement, qui bénéficieront automatiquement du traitement Cinematic avec l’effet bokeh. L’app peut également utiliser l’observation clé-valeur pour observer la propriété « cinematicVideoCaptureSceneMonitoringStatuses » et informer l’utilisateur lorsque la luminosité est insuffisante pour une bonne capture vidéo Cinematic.