Captura de quadros de vídeo
Este exemplo mostra como usar um capturador de vídeo personalizado, utilizando a câmera do dispositivo como fonte de vídeo. Para usar a implementação da câmera, instancie o capturador de câmera em seu VonageVideoManager:
publisher.videoCapture = BasicVideoCapturerCamera(preset: AVCaptureSession.Preset.cif352x288, desiredFrameRate: 30)
Inicialização e configuração do capturador de vídeo
O inicializador chama size(from:) para determinar a resolução e configura uma fila de envio serial para a captura de imagens, a fim de não afetar a fila da interface do usuário.
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)
}
A implementação de initCapture utiliza o AVFoundation estrutura para configurar a câmera para capturar imagens. Ela cria um AVCaptureSession, define o dispositivo de entrada e configura um 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)
}
Os quadros capturados com esse método são acessados por meio do AVCaptureVideoDataOutputSampleBufferDelegate.
A segunda parte de initCapture configura a taxa de quadros:
// 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))
O bestFrameRate(for:) O método retorna a melhor taxa de quadros para o 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 quadros para o vídeo da editora
O start O método inicia o AVCaptureSession:
//
// returns:
// - a negative value for error
// - 0 value when all is OK
//
func start() -> Int32 {
self.captureStarted = true
self.captureSession?.startRunning()
return 0
}
O método delegado captureOutput(_:didOutput:from:) é chamada quando um novo quadro de vídeo fica disponível.
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 o seguinte:
- Cria um
OTVideoFrameinstância. - Aloca um buffer de memória.
- Copia os dados da imagem do
CVImageBuffer(formato NV12) no buffer manual. O NV12 possui dois planos (Y e UV), que são copiados sequencialmente. - Marca o quadro com a data e a hora e a orientação.
- Chamadas
consumeFrame, passando o quadro para o SDK da Vonage.
Captura básica de vídeo
Saiba como configurar um capturador de vídeo básico usando o SDK de vídeo da Vonage.