Capture vidéo personnalisée (iOS)
Vue d'ensemble
Ce tutoriel vous guide à travers les étapes nécessaires pour apporter des modifications au module de capture vidéo de votre application OpenTok pour iOS.
Étape 1 : Initialisation de la capture
Le code de cette section se trouve dans la section Capteur vidéo de base projet de la dépôt « opentok-ios-sdk-samples »Si vous ne l'avez pas encore fait, vous devrez donc cloner le repo dans un répertoire local - Ceci peut être fait en utilisant la ligne de commande:
git clone https://github.com/opentok/opentok-ios-sdk-samples
Changez de répertoire pour accéder au projet Basic Video Capturer :
cd opentok-ios-sdk-samples/Basic-Video-Capturer
Installez ensuite la dépendance OpenTok :
pod install
Ce projet vous montre comment apporter des modifications mineures au capteur vidéo utilisé par la classe OTPublisher. Ouvrez le projet dans Xcode pour le suivre.
Dans cet exemple, l'application utilise un capteur vidéo personnalisé pour publier des pixels aléatoires (bruit blanc). Il s'agit simplement d'illustrer les principes de base de la configuration d'un capteur vidéo personnalisé. (Pour un exemple plus pratique, voir les exemples Camera Video Capturer et Screen Video Capturer, décrits dans les sections suivantes).
Dans le ViewController principal, après avoir appelé [_session publish:_publisher error:&error] pour initier la publication d'un flux audio-vidéo, le videoCapture de l'objet OTPublisher est définie comme une instance de OTKBasicVideoCapturer :
_publisher.videoCapture = [[OTKBasicVideoCapturer alloc] init];
OTKBasicVideoCapturer est une classe personnalisée qui implémente le protocole OTVideoCapture (défini dans le SDK OpenTok pour iOS). Ce protocole vous permet de définir un capteur vidéo personnalisé destiné à être utilisé par un éditeur OpenTok.
Les [OTVideoCapture initCapture:] initialise les paramètres de capture à utiliser par le capteur vidéo personnalisé. Dans l'implémentation personnalisée de OTVideoCapture de cet exemple (OTKBasicVideoCapturer), la méthode initCapture définit les propriétés de l'élément format de l'instance 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 classe OTVideoFormat (qui définit cette classe de format (propriété) est définie par le SDK OpenTok pour iOS. Dans cet exemple de code, le format du capteur vidéo est configuré pour utiliser ARGB comme format de pixel, avec un nombre spécifique d'octets par ligne, une hauteur spécifique et une largeur spécifique.
Les [OTVideoCapture setVideoCaptureConsumer] définit un objet OTVideoCaptureConsumer (défini par le SDK OpenTok pour iOS) que le consommateur vidéo utilise pour transmettre des images vidéo au flux de l'éditeur. Dans la classe OTKBasicVideoCapturer, cette méthode définit une instance locale d'OTVideoCaptureConsumer comme consommateur :
- (void)setVideoCaptureConsumer:(id<OTVideoCaptureConsumer>)videoCaptureConsumer
{
// Save consumer instance in order to use it to send frames to the session
self.consumer = videoCaptureConsumer;
}
Les [OTVideoCapture startCapture:] La méthode est appelée lorsqu'un éditeur commence à capturer une vidéo afin de la diffuser en continu vers la session OpenTok. Cela se produit après la [Session publish: error:] est appelée. Dans l'OTKBasicVideoCapturer de cette méthode, l'élément [self produceFrame] est appelée sur une file d'attente en arrière-plan après un intervalle défini :
- (int32_t)startCapture
{
self.captureStarted = YES;
dispatch_after(kTimerInterval,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),
^{
[self produceFrame];
});
return 0;
}
Les [self produceFrame] Cette méthode génère un objet OTVideoFrame (défini par le SDK OpenTok pour iOS) qui représente une image vidéo. Dans ce cas, l'image contient des pixels aléatoires qui remplissent la hauteur et la largeur définies pour le format vidéo d'exemple :
- (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];
});
}
}
La méthode transmet le cadre à la fonction [consumeFrame] de l'instance de l'OTVideoCaptureConsumer utilisée par ce capteur vidéo (décrit ci-dessus). L'éditeur envoie alors la trame de données au flux vidéo de la session.
Étape 2 : Capture d'images vidéo
Le code de cet exemple est également inclus dans le fichier Capteur vidéo de base projet de la dépôt « opentok-ios-sdk-samples ». Pour l'utiliser, décommentez la ligne suivante :
_publisher.videoCapture = [[OTKBasicVideoCapturerCamera alloc] initWithPreset:AVCaptureSessionPreset352x288 andDesiredFrameRate:30];
Commentez ensuite la ligne de la partie 1 :
// _publisher.videoCapture = [[OTKBasicVideoCapturer alloc] init];
Ce projet vous montre comment utiliser un capteur vidéo personnalisé en utilisant la caméra de l'appareil comme source vidéo.
Cet exemple de code utilise le cadre Apple AVFoundation pour capturer une vidéo à partir d'une caméra et la publier dans une session connectée. La classe ViewController crée une session, instancie les abonnés et configure l'éditeur. La classe captureOutput crée une image, effectue une capture d'écran, marque l'image avec un horodatage et l'enregistre dans une instance du consommateur. L'éditeur accède au consommateur pour obtenir la trame vidéo.
Comme cet exemple doit accéder à la caméra de l'appareil, vous devez le tester sur un appareil iOS. Vous ne pouvez pas le tester dans le simulateur iOS.
Initialisation et configuration du capteur vidéo
Les [OTKBasicVideoCapturerCamera initWithPreset: andDesiredFrameRate:] est un initialisateur de la classe OTKBasicVideoCapturerCamera. Elle appelle la méthode sizeFromAVCapturePreset pour définir la résolution de l'image. La taille de l'image et la fréquence d'images sont également définies ici. Une file d'attente distincte est créée pour la capture des images, afin de ne pas affecter la file d'attente de l'interface utilisateur.
- (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;
}
Les sizeFromAVCapturePreset identifie la valeur de la chaîne de caractères de la résolution de l'image dans le cadre iOS AVFoundation et renvoie une représentation CGSize.
La mise en œuvre de la [OTVideoCapture initCapture] utilise le cadre AVFoundation pour configurer la caméra afin de capturer des images. Dans la première partie de la méthode, une instance de AVCaptureVideoDataOutput est utilisée pour produire des images :
- (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 ...
}
Les images capturées à l'aide de cette méthode sont accessibles à l'aide de la fonction [AVCaptureVideoDataOutputSampleBufferDelegate captureOutput:didOutputSampleBuffer:fromConnection:] méthode déléguée. L'objet AVCaptureDevice représente la caméra et ses propriétés. Il fournit des images capturées à un objet AVCaptureSession.
La deuxième partie de la initCapture appelle la méthode bestFrameRateForDevice afin d'obtenir le meilleur taux de rafraîchissement pour la capture d'images :
- (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];
}
Les [self bestFrameRateForDevice] renvoie la meilleure fréquence d'images pour le dispositif de capture :
- (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;
}
Le cadre AVFoundation exige une plage minimale et maximale de fréquences d'images pour optimiser la qualité d'une capture d'image. Cette plage est définie dans le champ bestFrameRate objet. Pour des raisons de simplicité, les taux de rafraîchissement minimum et maximum sont identiques, mais vous pouvez définir vos propres taux de rafraîchissement minimum et maximum pour obtenir une meilleure qualité d'image en fonction de la vitesse de votre réseau. Dans cette application, la fréquence d'images et la résolution sont fixes.
Cette méthode définit le consommateur de capture vidéo, conformément au protocole OTVideoCaptureConsumer.
- (void)setVideoCaptureConsumer:(id<OTVideoCaptureConsumer>)videoCaptureConsumer
{
self.consumer = videoCaptureConsumer;
}
Les [OTVideoCapture captureSettings] définit le format des pixels et la taille de l'image utilisée par le capteur vidéo, en définissant les propriétés de l'objet OTVideoFormat.
Les [OTVideoCapture currentDeviceOrientation] Cette méthode interroge l'orientation de l'image dans le framework AVFoundation et renvoie son équivalent défini par l'énumération OTVideoOrientation dans le SDK OpenTok pour iOS.
Capturer des images pour la vidéo de l'éditeur
La mise en œuvre de la [OTVideoCapture startCapture] est appelée lorsque l'éditeur commence à capturer la vidéo à publier. Elle appelle la méthode [AVCaptureSession startRunning] de l'objet AVCaptureSession :
- (int32_t)startCapture
{
self.captureStarted = YES;
[self.captureSession startRunning];
return 0;
}
Les [AVCaptureVideoDataOutputSampleBufferDelegate captureOutput:didOutputSampleBuffer:fromConnection:] est appelée lorsqu'une nouvelle image vidéo est disponible à partir de la caméra.
- (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);
}
Cette méthode permet d'effectuer les opérations suivantes :
-
Crée une instance OTVideoFrame pour définir la nouvelle image vidéo.
-
Enregistre un tampon d'image dans la mémoire en fonction de la taille de l'image.
-
Écrit les données d'image de deux plans dans une mémoire tampon. Comme l'image est une NV12, ses données sont réparties sur deux plans. Il y a un plan pour les données Y et un plan pour les données UV. Une boucle for est exécutée pour parcourir les deux plans et écrire leurs données dans une mémoire tampon.
-
Crée un horodatage pour marquer une image capturée. Chaque image est marquée d'un horodatage afin que l'éditeur et l'abonné puissent créer la même chronologie et référencer les images dans le même ordre.
-
Appelle le
[OTVideoCaptureConsumer consumeFrame:]en transmettant l'objet OTVideoFrame. L'éditeur envoie alors la trame dans le flux qu'il publie.
La mise en œuvre de la [AVCaptureVideoDataOutputSampleBufferDelegate captureOutput:didDropSampleBuffer:fromConnection] est appelée chaque fois qu'il y a un retard dans la réception des trames. Elle laisse tomber les trames pour que la publication dans la session se poursuive sans interruption :
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection
{
NSLog(@"Frame dropped");
}
Autres remarques concernant l'application
Le protocole OTVideoCapture comprend d'autres méthodes obligatoires, qui sont implémentées par la classe OTKBasicVideoCapturerCamera. Cependant, cet exemple ne fait rien d'intéressant dans ces méthodes ; elles ne sont donc pas abordées ici.
Félicitations ! Vous venez de terminer le tutoriel sur la capture vidéo personnalisée sous iOS.
Vous pouvez continuer à tester et à modifier le code que vous avez développé ici, ou consulter le Prochaines étapes ci-dessous.
Prochaines étapes
Une fois que vous aurez terminé cette étape, poursuivez le développement et l'amélioration de votre application OpenTok à l'aide des ressources utiles suivantes :
- Notions de base sur la Video API — Si ce n'est pas déjà fait, prenez quelques minutes pour découvrir le fonctionnement exact d'OpenTok
- Exemples de code — une sélection d'applications types qui explorent diverses fonctionnalités et cas d'utilisation d'OpenTok
- Guides du développeur — documentation détaillée sur toutes les fonctionnalités d'OpenTok
- Tutoriels supplémentaires — guides pas à pas pour la mise en œuvre d'autres fonctionnalités et cas d'utilisation d'OpenTok
- Référence du SDK iOS — découvrez les méthodes et événements spécifiques associés au SDK iOS d'OpenTok