-
Captura videos cinematográficos en tu app
Descubre cómo con la API Cinematic Video tu app puede capturar videos de estilo cinematográfico. Mencionaremos cómo configurar una sesión de captura cinematográfica y los fundamentos de la creación de una IU de captura de video. Y exploraremos funcionalidades cinematográficas avanzadas, como los efectos de profundidad de campo para lograr el seguimiento y el enfoque en rack.
Capítulos
- 0:00 - Introducción
- 0:33 - Video cinematográfico
- 3:44 - Crea una gran experiencia de captura cinematográfica
Recursos
Videos relacionados
WWDC25
WWDC24
WWDC23
-
Buscar este video…
-
-
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 - Introducción
Usa la API Cinematic Video para capturar videos de estilo cinematográfico de nivel profesional en tus apps. El iPhone 13 y 13 Pro presentaron el modo Cine, que transformó al iPhone en una potencia cinematográfica.
- 0:33 - Video cinematográfico
El Video cinematográfico utiliza poca profundidad de campo y seguimiento del enfoque para guiar la atención del espectador, imitando técnicas cinematográficas. La API Cinematic Video en iOS 26 simplifica este proceso, permitiendo que las apps enfoquen y rastreen automáticamente. Para crear una experiencia de captura cinematográfica, configura una sesión de captura, selecciona un dispositivo y, a continuación, habilita la captura de video cinematográfica configurando “isCinematicVideoCaptureEnabled” como true en la clase “AVCaptureDeviceInput”. Esto configura la sesión para emitir video cinemático con datos de disparidad, metadatos y el video original, lo que permite una edición no destructiva. Puedes reproducir o editar el renderizado de bokeh con Cinematic Framework.
- 3:44 - Crea una gran experiencia de captura cinematográfica
El ejemplo comienza configurando una “AVCaptureSession” para habilitar la captura de video cinematográfico en dispositivos compatibles, como la cámara amplia doble en la parte posterior y la cámara TrueDepth en la parte frontal. El ejemplo selecciona un formato de video apropiado, donde “isCinematicVideoCaptureSupported” retorna como true, y, a continuación, agrega entrada de audio del micrófono a la sesión. La captura de audio espacial está habilitada para mejorar la experiencia cinematográfica. Para obtener más información sobre audio espacial, consulta “Mejora las capacidades de creación de contenido de audio de tu app”. A continuación, se habilita la estabilización de video para mejorar la experiencia del usuario y se obtiene una vista previa de la sesión de captura mediante una vista SwiftUI. Luego, el ejemplo crea una estructura representable personalizada para envolver “AVCaptureVideoPreviewLayer”, lo que permite integrarlo perfectamente en la interfaz SwiftUI. El ejemplo luego profundiza en el control del efecto de video cinematográfico, específicamente la profundidad de campo reducida. Al ajustar “simulatedAperture”, se puede fortalecer o debilitar el efecto bokeh, que brinda un control más creativo sobre el video. Para habilitar el control del enfoque manual, el ejemplo implementa la detección de metadatos para identificar candidatos al enfoque, como rostros. Luego, dibuja rectángulos en la pantalla para representar a estos candidatos, lo que permite a los usuarios tocar y centrarse en elementos específicos. La API Cinematic Video proporciona varios métodos para controlar el enfoque durante la grabación de video. La API genera objetos de metadatos que incluyen información sobre los sujetos detectados en el marco. El parámetro de configuración “focusMode” determina el comportamiento de seguimiento del video cinematográfico. Hay tres casos para esta enumeración: “ninguno”, “fuerte” y “débil”. El enfoque fuerte se centra en un sujeto, ignorando otros posibles candidatos para el enfoque. El enfoque débil permite que el algoritmo ajuste automáticamente el enfoque en función de la escena. El caso ninguno se utiliza principalmente para determinar el estado del enfoque en lugar de establecerlo. La API ofrece tres métodos de enfoque: el método “setCinematicVideoTrackingFocus” toma un ID de objeto detectado como entrada y establece el foco en ese objeto. El método “setCinematicVideoTrackingFocus” toma un punto en la vista como entrada. A continuación, el video cinematográfico busca un objeto interesante en ese punto y crea un nuevo objeto de metadatos del tipo “objeto destacado”, en el que luego se puede centrarse. “setCinematicVideoFixedFocus” establece un foco fijo en un punto específico de la escena, calculando la distancia internamente mediante señales de profundidad. Cuando se combina con un modo de enfoque fuerte, bloquea el enfoque en un plano particular, ignorando otras actividades en la escena. Puedes implementar lógica de enfoque personalizada en tus apps. Por ejemplo, al tocar un rectángulo de detección se puede cambiar el enfoque entre elementos, y un toque prolongado puede establecer un enfoque fijo fuerte. La app diferencia visualmente entre débil, fuerte y sin foco para guiar al usuario. Además, la API permite la captura de imágenes fijas durante la grabación, que recibirán automáticamente el tratamiento cinematográfico con el efecto bokeh. La app también puede usar la observación de valor clave para observar la propiedad “cinematicVideoCaptureSceneMonitoringStatuses” e informar al usuario cuando no hay suficiente luz para una captura de video cinematográfica adecuada.