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:
- Crea un
OTVideoFrameinstancia. - Asigna un búfer de memoria.
- 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. - Etiqueta el fotograma con una marca de tiempo y una orientación.
- Llamadas
consumeFramepasando el marco al SDK de Vonage.
Captura de vídeo básica
Aprende a configurar una capturadora de video básica usando el SDK de video de Vonage.