Swift

Capture d'images vidéo

Cet exemple vous montre comment utiliser un capteur vidéo personnalisé en utilisant la caméra de l'appareil comme source vidéo. Pour utiliser l'implémentation de la caméra, instanciez le capteur de caméra dans votre fichier VonageVideoManager:

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

Initialisation et configuration du capteur vidéo

L'initialisateur appelle size(from:) pour déterminer la résolution et met en place une file d'attente sérielle pour la capture des images, afin de ne pas affecter la file d'attente de l'interface utilisateur.

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 mise en œuvre de la initCapture utilise le AVFoundation pour configurer l'appareil photo afin de capturer des images. Il crée un AVCaptureSession, définit le périphérique d'entrée, et configure 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)
}

Les images capturées à l'aide de cette méthode sont accessibles par l'intermédiaire de la fonction AVCaptureVideoDataOutputSampleBufferDelegate.

La deuxième partie de la initCapture configure la fréquence d'images :

    // 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))

Les bestFrameRate(for:) renvoie la meilleure fréquence d'images pour le dispositif de capture :

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
}

Capturer des images pour la vidéo de l'éditeur

Les start démarre la méthode AVCaptureSession:

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

La méthode de délégation captureOutput(_:didOutput:from:) est appelée lorsqu'une nouvelle image vidéo est 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)
}

Cette méthode permet d'effectuer les opérations suivantes :

  1. Crée un OTVideoFrame instance.
  2. Alloue un tampon de mémoire.
  3. Copie les données d'image de l'écran CVImageBuffer (format NV12) dans la mémoire tampon manuelle. Le format NV12 comporte deux plans (Y et UV), qui sont copiés de manière séquentielle.
  4. Marque l'image avec un horodatage et une orientation.
  5. Appels consumeFrameet transmet le cadre au SDK de Vonage.