Swift

Captura de fotogramas de vídeo

Este ejemplo muestra cómo utilizar un capturador de vídeo personalizado utilizando la cámara del dispositivo como fuente de vídeo. Para utilizar la implementación de la cámara, instancie el capturador de cámara en su archivo VonageVideoManager:

publisher.videoCapture = BasicVideoCapturerCamera(preset: AVCaptureSession.Preset.cif352x288, desiredFrameRate: 30)

Inicialización y configuración del capturador de vídeo

El inicializador llama a size(from:) para determinar la resolución y establece una cola de envío en serie para capturar imágenes, con el fin de no afectar a la cola de la interfaz de usuario.

init(preset: AVCaptureSession.Preset, desiredFrameRate: Int) {
    self.sessionPreset = preset
    self.desiredFrameRate = desiredFrameRate
    self.captureQueue = DispatchQueue(label: "com.vonage.BasicVideoCapturer")
    
    super.init()
    
    let size = self.size(from: self.sessionPreset)
    self.imageWidth = Int(size.width)
    self.imageHeight = Int(size.height)
}

La aplicación de initCapture utiliza el AVFoundation para configurar la cámara para capturar imágenes. Crea un AVCaptureSession, establece el dispositivo de entrada y configura un AVCaptureVideoDataOutput:

func initCapture() {
    let session = AVCaptureSession()
    session.beginConfiguration()
    
    // Set device capture
    session.sessionPreset = sessionPreset
    
    guard let videoDevice = AVCaptureDevice.default(for: .video),
          let deviceInput = try? AVCaptureDeviceInput(device: videoDevice) else { return }
          
    self.inputDevice = deviceInput
    if session.canAddInput(deviceInput) {
        session.addInput(deviceInput)
    }
    
    let outputDevice = AVCaptureVideoDataOutput()
    outputDevice.alwaysDiscardsLateVideoFrames = true
    outputDevice.videoSettings = [
        kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)
    ]
    
    outputDevice.setSampleBufferDelegate(self, queue: captureQueue)
    if session.canAddOutput(outputDevice) {
        session.addOutput(outputDevice)
    }
    
    // ... Frame rate configuration (see below)
}

Se accede a los fotogramas capturados con este método a través de la función AVCaptureVideoDataOutputSampleBufferDelegate.

La segunda parte de initCapture configura la velocidad de fotogramas:

    // Set framerate
    let bestFPS = self.bestFrameRate(for: videoDevice)
    do {
        try videoDevice.lockForConfiguration()
        let duration = CMTime(value: 1, timescale: CMTimeScale(bestFPS))
        videoDevice.activeVideoMinFrameDuration = duration
        videoDevice.activeVideoMaxFrameDuration = duration
        videoDevice.unlockForConfiguration()
    } catch {
        print("Error locking configuration")
    }
    
    session.commitConfiguration()
    self.captureSession = session
    
    self.format = OTVideoFormat(nv12WithWidth: UInt32(imageWidth), height: UInt32(imageHeight))

En bestFrameRate(for:) devuelve la mejor frecuencia de imagen para el dispositivo de captura:

private func bestFrameRate(for device: AVCaptureDevice) -> Double {
    var bestRate: Double = 0
    for range in device.activeFormat.videoSupportedFrameRateRanges {
        let duration = range.minFrameDuration
        let currentRate = Double(duration.timescale) / Double(duration.value)
        
        if currentRate > bestRate && currentRate < Double(desiredFrameRate) {
            bestRate = currentRate
        }
    }
    return bestRate
}

Captura de fotogramas para el vídeo del editor

En start inicia el método AVCaptureSession:

//
//  returns:
//  - a negative value for error
//  - 0 value when all is OK
//
func start() -> Int32 {
    self.captureStarted = true
    self.captureSession?.startRunning()
    return 0
}

El método delegado captureOutput(_:didOutput:from:) se llama cuando hay un nuevo fotograma de vídeo disponible.

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    guard captureStarted, let format = self.format else { return }
    guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
    
    let frame = OTVideoFrame(format: format)
    
    // Prepare memory copy for NV12 (2 planes)
    let planeCount = CVPixelBufferGetPlaneCount(imageBuffer)
    let totalSize = CVPixelBufferGetDataSize(imageBuffer)
    let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: totalSize)
    
    CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)
    
    var planePointers = [UnsafeMutablePointer<UInt8>?]()
    var currentDestination = buffer
    
    // Copy planes
    for i in 0..<planeCount {
        guard let sourceBaseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i) else { continue }
        let planeSize = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, i) * CVPixelBufferGetHeightOfPlane(imageBuffer, i)
        
        planePointers.append(currentDestination)
        memcpy(currentDestination, sourceBaseAddress, planeSize)
        currentDestination += planeSize
    }
    
    // Set metadata and consume
    if let device = self.inputDevice?.device {
         let minDuration = device.activeVideoMinFrameDuration
         frame.format?.estimatedFramesPerSecond = Double(minDuration.timescale) / Double(minDuration.value)
    }
    
    frame.orientation = self.currentDeviceOrientation()
    frame.timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
    
    planePointers.withUnsafeBufferPointer { bufferPointer in
        frame.setPlanesWithPointers(bufferPointer.baseAddress, numPlanes: Int(planeCount))
    }
    
    videoCaptureConsumer?.consumeFrame(frame)
    
    buffer.deallocate()
    CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly)
}

Este método realiza lo siguiente:

  1. Crea un OTVideoFrame instancia.
  2. Asigna un búfer de memoria.
  3. Copia los datos de imagen del CVImageBuffer (formato NV12) en el búfer manual. NV12 tiene dos planos (Y y UV), que se copian secuencialmente.
  4. Etiqueta el fotograma con una marca de tiempo y una orientación.
  5. Llamadas consumeFramepasando el marco al SDK de Vonage.