Captura de vídeo personalizada (iOS)
Visión general
Este tutorial explica paso a paso los pasos necesarios para realizar modificaciones en el capturador de vídeo de tu aplicación OpenTok para iOS.
Paso 1: Inicialización de la captura
El código de esta sección se encuentra en el Capturador de vídeo básico proyecto de la Repositorio «opentok-ios-sdk-samples»por lo que, si aún no lo ha hecho, deberá clonar el repositorio en un directorio local. esto se puede hacer utilizando la línea de comandos:
git clone https://github.com/opentok/opentok-ios-sdk-samples
Cambia de directorio al proyecto Capturador de Vídeo Básico:
cd opentok-ios-sdk-samples/Basic-Video-Capturer
A continuación, instala la dependencia de OpenTok:
pod install
Este proyecto muestra cómo hacer pequeñas modificaciones en el capturador de vídeo utilizado por la clase OTPublisher. Abre el proyecto en Xcode para seguirlo.
En este ejemplo, la aplicación utiliza un capturador de vídeo personalizado para mostrar píxeles aleatorios (ruido blanco). Esto se hace simplemente para ilustrar los principios básicos de la configuración de un capturador de vídeo personalizado. (Para ver un ejemplo más práctico, consulta los ejemplos «Capturador de vídeo de cámara» y «Capturador de vídeo de pantalla», descritos en las secciones siguientes).
En el ViewController principal, después de llamar a [_session publish:_publisher error:&error] para iniciar la publicación de un flujo de audio-vídeo, el videoCapture del objeto OTPublisher es una instancia de OTKBasicVideoCapturer:
_publisher.videoCapture = [[OTKBasicVideoCapturer alloc] init];
OTKBasicVideoCapturer es una clase personalizada que implementa el protocolo OTVideoCapture (definido en el SDK de OpenTok para iOS). Este protocolo permite definir un capturador de vídeo personalizado para que lo utilice un emisor de OpenTok.
El [OTVideoCapture initCapture:] El método inicializa los parámetros de captura que utilizará el capturador de vídeo personalizado. En la implementación personalizada de OTVideoCapture (OTKBasicVideoCapturer) de este ejemplo, el initCapture establece las propiedades del método format propiedad de la instancia de OTVideoCapture:
- (void)initCapture
{
self.format = [[OTVideoFormat alloc] init];
self.format.pixelFormat = OTPixelFormatARGB;
self.format.bytesPerRow = [@[@(kImageWidth * 4)] mutableCopy];
self.format.imageHeight = kImageHeight;
self.format.imageWidth = kImageWidth;
}
La clase OTVideoFormat (que define esto format (propiedad) viene definida por el SDK de OpenTok para iOS. En este código de ejemplo, el formato del capturador de vídeo está configurado para utilizar ARGB como formato de píxeles, con un número específico de bytes por fila, una altura específica y una anchura específica.
El [OTVideoCapture setVideoCaptureConsumer] Establece un objeto OTVideoCaptureConsumer (definido por el SDK de OpenTok para iOS) que el consumidor de vídeo utiliza para transmitir fotogramas de vídeo a la transmisión del editor. En OTKBasicVideoCapturer, este método establece una instancia local de OTVideoCaptureConsumer como consumidor:
- (void)setVideoCaptureConsumer:(id<OTVideoCaptureConsumer>)videoCaptureConsumer
{
// Save consumer instance in order to use it to send frames to the session
self.consumer = videoCaptureConsumer;
}
El [OTVideoCapture startCapture:] El método se invoca cuando un emisor comienza a grabar vídeo para enviarlo como transmisión a la sesión de OpenTok. Esto ocurrirá después de que el [Session publish: error:] es llamado. En el OTKBasicVideoCapturer de este método, la función [self produceFrame] es llamado en una cola de fondo después de un intervalo establecido:
- (int32_t)startCapture
{
self.captureStarted = YES;
dispatch_after(kTimerInterval,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),
^{
[self produceFrame];
});
return 0;
}
El [self produceFrame] El método genera un objeto OTVideoFrame (definido por el SDK de OpenTok para iOS) que representa un fotograma de vídeo. En este caso, el fotograma contiene píxeles aleatorios que ocupan la altura y la anchura definidas para el formato de vídeo de muestra:
- (void)produceFrame
{
OTVideoFrame *frame = [[OTVideoFrame alloc] initWithFormat:self.format];
// Generate a image with random pixels
u_int8_t *imageData[1];
imageData[0] = malloc(sizeof(uint8_t) * kImageHeight * kImageWidth * 4);
for (int i = 0; i < kImageWidth * kImageHeight * 4; i+=4) {
imageData[0][i] = rand() % 255; // A
imageData[0][i+1] = rand() % 255; // R
imageData[0][i+2] = rand() % 255; // G
imageData[0][i+3] = rand() % 255; // B
}
[frame setPlanesWithPointers:imageData numPlanes:1];
[self.consumer consumeFrame:frame];
free(imageData[0]);
if (self.captureStarted) {
dispatch_after(kTimerInterval,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),
^{
[self produceFrame];
});
}
}
El método pasa el marco al [consumeFrame] de la instancia de OTVideoCaptureConsumer utilizada por este capturador de vídeo (descrito anteriormente). Esto hace que el editor envíe el fotograma de datos al flujo de vídeo de la sesión.
Paso 2: Captura de fotogramas de vídeo
El código de este ejemplo también se incluye en el Capturador de vídeo básico proyecto de la Repositorio «opentok-ios-sdk-samples». Para utilizarlo, descomenta la siguiente línea:
_publisher.videoCapture = [[OTKBasicVideoCapturerCamera alloc] initWithPreset:AVCaptureSessionPreset352x288 andDesiredFrameRate:30];
A continuación, comenta la línea de la parte 1:
// _publisher.videoCapture = [[OTKBasicVideoCapturer alloc] init];
Este proyecto muestra cómo utilizar un capturador de vídeo personalizado utilizando la cámara del dispositivo como fuente de vídeo.
Este código de ejemplo utiliza el marco AVFoundation de Apple para capturar vídeo desde una cámara y publicarlo en una sesión conectada. La clase ViewController crea una sesión, instancia los suscriptores y configura el editor. El captureOutput crea un fotograma, realiza una captura de pantalla, etiqueta el fotograma con una marca de tiempo y lo guarda en una instancia del consumidor. El editor accede al consumidor para obtener el fotograma de vídeo.
Ten en cuenta que, dado que este ejemplo necesita acceder a la cámara del dispositivo, debes probarlo en un dispositivo iOS. No puedes probarlo en el simulador de iOS.
Inicialización y configuración del capturador de vídeo
El [OTKBasicVideoCapturerCamera initWithPreset: andDesiredFrameRate:] El método es un inicializador de la clase OTKBasicVideoCapturerCamera. Llama al sizeFromAVCapturePreset para establecer la resolución de la imagen. El tamaño de la imagen y la velocidad de fotogramas también se establecen aquí. Se crea una cola separada para capturar imágenes, para no afectar a la cola de la interfaz de usuario.
- (id)initWithPreset:(NSString *)preset andDesiredFrameRate:(NSUInteger)frameRate
{
self = [super init];
if (self) {
self.sessionPreset = preset;
CGSize imageSize = [self sizeFromAVCapturePreset:self.sessionPreset];
_imageHeight = imageSize.height;
_imageWidth = imageSize.width;
_desiredFrameRate = frameRate;
_captureQueue = dispatch_queue_create("com.tokbox.OTKBasicVideoCapturer",
DISPATCH_QUEUE_SERIAL);
}
return self;
}
El sizeFromAVCapturePreset identifica el valor de cadena de la resolución de la imagen en el marco AVFoundation de iOS y devuelve una representación CGSize.
La aplicación de la [OTVideoCapture initCapture] El método utiliza el marco AVFoundation para configurar la cámara para que capture imágenes. En la primera parte del método se utiliza una instancia de AVCaptureVideoDataOutput para generar fotogramas de imagen:
- (void)initCapture
{
NSError *error;
self.captureSession = [[AVCaptureSession alloc] init];
[self.captureSession beginConfiguration];
// Set device capture
self.captureSession.sessionPreset = self.sessionPreset;
AVCaptureDevice *videoDevice =
[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
self.inputDevice =
[AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
[self.captureSession addInput:self.inputDevice];
AVCaptureVideoDataOutput *outputDevice = [[AVCaptureVideoDataOutput alloc] init];
outputDevice.alwaysDiscardsLateVideoFrames = YES;
outputDevice.videoSettings =
@{(NSString *)kCVPixelBufferPixelFormatTypeKey:
@(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
)};
[outputDevice setSampleBufferDelegate:self queue:self.captureQueue];
[self.captureSession addOutput:outputDevice];
// See the next section ...
}
Se puede acceder a los fotogramas capturados con este método mediante el [AVCaptureVideoDataOutputSampleBufferDelegate captureOutput:didOutputSampleBuffer:fromConnection:] método delegado. El objeto AVCaptureDevice representa la cámara y sus propiedades. Proporciona las imágenes capturadas a un objeto AVCaptureSession.
La segunda parte de la initCapture llama al método bestFrameRateForDevice Método para obtener la mejor frecuencia de fotogramas en la captura de imágenes:
- (void)initCapture
{
// See previous section ...
// Set framerate
double bestFrameRate = [self bestFrameRateForDevice];
CMTime desiredMinFrameDuration = CMTimeMake(1, bestFrameRate);
CMTime desiredMaxFrameDuration = CMTimeMake(1, bestFrameRate);
[self.inputDevice.device lockForConfiguration:&error];
self.inputDevice.device.activeVideoMaxFrameDuration = desiredMaxFrameDuration;
self.inputDevice.device.activeVideoMinFrameDuration = desiredMinFrameDuration;
[self.captureSession commitConfiguration];
self.format = [OTVideoFormat videoFormatNV12WithWidth:self.imageWidth
height:self.imageHeight];
}
El [self bestFrameRateForDevice] devuelve la mejor frecuencia de imagen para el dispositivo de captura:
- (double)bestFrameRateForDevice
{
double bestFrameRate = 0;
for (AVFrameRateRange* range in
self.inputDevice.device.activeFormat.videoSupportedFrameRateRanges)
{
CMTime currentDuration = range.minFrameDuration;
double currentFrameRate = currentDuration.timescale / currentDuration.value;
if (currentFrameRate > bestFrameRate && currentFrameRate < self.desiredFrameRate) {
bestFrameRate = currentFrameRate;
}
}
return bestFrameRate;
}
El marco AVFoundation requiere un rango mínimo y máximo de frecuencias de fotogramas para optimizar la calidad de la captura de imágenes. Este rango se configura en el bestFrameRate objeto. Para simplificar, la frecuencia de imagen mínima y máxima se establece como el mismo número, pero es posible que desee establecer sus propias frecuencias de imagen mínima y máxima para obtener una mejor calidad de imagen en función de la velocidad de su red. En esta aplicación, la frecuencia de imagen y la resolución son fijas.
Este método establece el consumidor de captura de vídeo, definido por el protocolo OTVideoCaptureConsumer.
- (void)setVideoCaptureConsumer:(id<OTVideoCaptureConsumer>)videoCaptureConsumer
{
self.consumer = videoCaptureConsumer;
}
El [OTVideoCapture captureSettings] establece el formato de píxel y el tamaño de la imagen utilizada por el capturador de vídeo, estableciendo las propiedades del objeto OTVideoFormat.
El [OTVideoCapture currentDeviceOrientation] Este método consulta la orientación de la imagen en el marco AVFoundation y devuelve su equivalente definido por la enumeración OTVideoOrientation en el SDK de OpenTok para iOS.
Captura de fotogramas para el vídeo del editor
La aplicación de la [OTVideoCapture startCapture] se ejecuta cuando el editor empieza a capturar vídeo para publicarlo. Llama al método [AVCaptureSession startRunning] método del objeto AVCaptureSession:
- (int32_t)startCapture
{
self.captureStarted = YES;
[self.captureSession startRunning];
return 0;
}
El [AVCaptureVideoDataOutputSampleBufferDelegate captureOutput:didOutputSampleBuffer:fromConnection:] es llamado cuando un nuevo fotograma de vídeo está disponible desde la cámara.
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection
{
if (!self.captureStarted)
return;
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
OTVideoFrame *frame = [[OTVideoFrame alloc] initWithFormat:self.format];
NSUInteger planeCount = CVPixelBufferGetPlaneCount(imageBuffer);
uint8_t *buffer = malloc(sizeof(uint8_t) * CVPixelBufferGetDataSize(imageBuffer));
uint8_t *dst = buffer;
uint8_t *planes[planeCount];
CVPixelBufferLockBaseAddress(imageBuffer, 0);
for (int i = 0; i < planeCount; i++) {
size_t planeSize = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, i)
* CVPixelBufferGetHeightOfPlane(imageBuffer, i);
planes[i] = dst;
dst += planeSize;
memcpy(planes[i],
CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i),
planeSize);
}
CMTime minFrameDuration = self.inputDevice.device.activeVideoMinFrameDuration;
frame.format.estimatedFramesPerSecond = minFrameDuration.timescale / minFrameDuration.value;
frame.format.estimatedCaptureDelay = 100;
frame.orientation = [self currentDeviceOrientation];
CMTime time = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
frame.timestamp = time;
[frame setPlanesWithPointers:planes numPlanes:planeCount];
[self.consumer consumeFrame:frame];
free(buffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
}
Este método hace lo siguiente:
-
Crea una instancia de OTVideoFrame para definir el nuevo fotograma de vídeo.
-
Guarda un búfer de imagen de la memoria en función del tamaño de la imagen.
-
Escribe datos de imagen de dos planos en un búfer de memoria. Como la imagen es una NV12, sus datos se distribuyen en dos planos. Hay un plano para los datos Y y un plano para los datos UV. Se ejecuta un bucle for para recorrer ambos planos y escribir sus datos en una memoria intermedia.
-
Crea una marca de tiempo para etiquetar una imagen capturada. Cada imagen se etiqueta con una marca de tiempo para que tanto el editor como el suscriptor puedan crear la misma línea de tiempo y referenciar los fotogramas en el mismo orden.
-
Llama al
[OTVideoCaptureConsumer consumeFrame:]pasando el objeto OTVideoFrame. Esto hace que el editor envíe el fotograma en el flujo que publica.
La aplicación de la [AVCaptureVideoDataOutputSampleBufferDelegate captureOutput:didDropSampleBuffer:fromConnection] es llamado cada vez que hay un retraso en la recepción de tramas. Deja caer tramas para seguir publicando en la sesión sin interrupciones:
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection
{
NSLog(@"Frame dropped");
}
Otras notas sobre la aplicación
El protocolo OTVideoCapture incluye otros métodos obligatorios, que son implementados por la clase OTKBasicVideoCapturerCamera. Sin embargo, este ejemplo no realiza ninguna acción relevante en dichos métodos, por lo que no se incluyen en este análisis.
¡Enhorabuena! Has completado el tutorial sobre la captura de vídeo personalizada para iOS.
Puedes seguir probando y modificando el código que has creado aquí, o echar un vistazo a la Próximos pasos abajo.
Próximos pasos
Cuando hayas terminado aquí, sigue desarrollando y mejorando tu aplicación de OpenTok con estos útiles recursos:
- Conceptos básicos de la Video API: si aún no lo has hecho, dedica unos minutos a descubrir exactamente cómo funciona OpenTok
- Ejemplos de código: una recopilación de aplicaciones de ejemplo que exploran diversas funciones y casos de uso de OpenTok
- Guías para desarrolladores: documentación detallada sobre todas las características y funcionalidades de OpenTok
- Tutoriales adicionales: guías paso a paso para implementar otras funciones y casos de uso de OpenTok
- Referencia del SDK de iOS: descubre los métodos y eventos específicos asociados al SDK de OpenTok para iOS