Benutzerdefinierte Videoaufzeichnung (iOS)

Übersicht

Dieses Tutorial führt Sie durch die Schritte, die erforderlich sind, um Änderungen am Video-Capturer in Ihrer OpenTok-iOS-Application vorzunehmen.

Schritt 1: Initialisierung der Erfassung

Der Code für diesen Abschnitt befindet sich in der Basic Video Capturer Projekt des Repo „opentok-ios-sdk-samples“Wenn Sie es also noch nicht getan haben, müssen Sie das Repo in ein lokales Verzeichnis klonen - Dies kann über die Befehlszeile erfolgen:

git clone https://github.com/opentok/opentok-ios-sdk-samples

Wechseln Sie in das Verzeichnis des Basic Video Capturer-Projekts:

cd opentok-ios-sdk-samples/Basic-Video-Capturer

Installieren Sie anschließend die OpenTok-Abhängigkeit:

pod install

Dieses Projekt zeigt Ihnen, wie Sie kleinere Änderungen an dem von der Klasse OTPublisher verwendeten Video-Capturer vornehmen können. Öffnen Sie das Projekt in Xcode, um es nachzuvollziehen.

In diesem Beispiel verwendet die Anwendung einen benutzerdefinierten Video-Capturer, um zufällige Pixel (weißes Rauschen) zu veröffentlichen. Dies dient lediglich zur Veranschaulichung der grundlegenden Prinzipien beim Einrichten eines benutzerdefinierten Video-Capturers. (Ein praktischeres Beispiel finden Sie in den Beispielen Kamera-Video-Capturer und Bildschirm-Video-Capturer, die in den folgenden Abschnitten beschrieben werden).

Im Haupt-ViewController, nach dem Aufruf von [_session publish:_publisher error:&error] um die Veröffentlichung eines Audio-Video-Streams zu veranlassen, die videoCapture des OTPublisher-Objekts wird auf eine Instanz von OTKBasicVideoCapturer gesetzt:

_publisher.videoCapture = [[OTKBasicVideoCapturer alloc] init];

OTKBasicVideoCapturer ist eine benutzerdefinierte Klasse, die das OTVideoCapture-Protokoll (definiert im OpenTok iOS SDK) implementiert. Mit diesem Protokoll können Sie einen benutzerdefinierten Video-Capturer definieren, der von einem OpenTok-Publisher verwendet wird.

Die [OTVideoCapture initCapture:] Methode initialisiert die Aufnahmeeinstellungen, die von dem benutzerdefinierten Video-Capturer verwendet werden sollen. In der benutzerdefinierten Implementierung von OTVideoCapture (OTKBasicVideoCapturer) in diesem Beispiel wird die initCapture Methode setzt Eigenschaften der format Eigenschaft der OTVideoCapture-Instanz:

- (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;
}

Die Klasse OTVideoFormat (die dieses Format definiert) format (Eigenschaft) wird durch das OpenTok iOS SDK definiert. In diesem Beispielcode ist das Format des Video-Capturers so eingestellt, dass ARGB als Pixelformat verwendet wird, mit einer bestimmten Anzahl von Bytes pro Zeile, einer bestimmten Höhe und einer bestimmten Breite.

Die [OTVideoCapture setVideoCaptureConsumer] Legt ein OTVideoCaptureConsumer-Objekt (definiert durch das OpenTok iOS SDK) fest, das der Video-Consumer verwendet, um Videobilder an den Stream des Publishers zu übertragen. Im OTKBasicVideoCapturer legt diese Methode eine lokale OTVideoCaptureConsumer-Instanz als Consumer fest:

- (void)setVideoCaptureConsumer:(id<OTVideoCaptureConsumer>)videoCaptureConsumer
{
    // Save consumer instance in order to use it to send frames to the session
    self.consumer = videoCaptureConsumer;
}

Die [OTVideoCapture startCapture:] Die Methode wird aufgerufen, wenn ein Publisher mit der Videoaufnahme beginnt, um das Video als Stream an die OpenTok-Sitzung zu senden. Dies geschieht nach dem [Session publish: error:] Methode aufgerufen wird. In dem OTKBasicVideoCapturer dieser Methode wird die [self produceFrame] Methode wird nach einem festgelegten Intervall in einer Hintergrundwarteschlange aufgerufen:

- (int32_t)startCapture
{
    self.captureStarted = YES;
    dispatch_after(kTimerInterval,
                   dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),
                   ^{
                       [self produceFrame];
                   });

    return 0;
}

Die [self produceFrame] Die Methode generiert ein OTVideoFrame-Objekt (definiert durch das OpenTok iOS SDK), das einen Videobildrahmen darstellt. In diesem Fall enthält der Bildrahmen zufällige Pixel, die die für das Beispielvideoformat definierte Höhe und Breite ausfüllen:

- (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];
                       });
    }
}

Die Methode übergibt den Rahmen an die [consumeFrame] Methode der Instanz von OTVideoCaptureConsumer, die von diesem Video-Capturer verwendet wird (siehe oben). Dies veranlasst den Herausgeber, das Datenbild an den Videostream in der Sitzung zu senden.

Schritt 2: Video-Bilder erfassen

Der Code für dieses Beispiel ist auch in der Basic Video Capturer Projekt des Repo „opentok-ios-sdk-samples“. Um diese Funktion zu nutzen, entfernen Sie das Kommentarzeichen vor der folgenden Zeile:

_publisher.videoCapture = [[OTKBasicVideoCapturerCamera alloc] initWithPreset:AVCaptureSessionPreset352x288 andDesiredFrameRate:30];

Dann kommentieren Sie die Zeile aus Teil 1 aus:

// _publisher.videoCapture = [[OTKBasicVideoCapturer alloc] init];

Dieses Projekt zeigt Ihnen, wie Sie einen benutzerdefinierten Video-Capturer verwenden, der die Gerätekamera als Videoquelle nutzt.

Dieser Beispielcode verwendet das Apple AVFoundation-Framework, um Videos von einer Kamera aufzunehmen und in einer verbundenen Sitzung zu veröffentlichen. Die ViewController-Klasse erstellt eine Sitzung, instanziiert Abonnenten und richtet den Herausgeber ein. Die captureOutput Methode erstellt ein Bild, nimmt einen Screenshot auf, versieht das Bild mit einem Zeitstempel und speichert es in einer Instanz von consumer. Der Herausgeber greift auf den Verbraucher zu, um das Videobild zu erhalten.

Da dieses Beispiel auf die Kamera des Geräts zugreifen muss, müssen Sie es auf einem iOS-Gerät testen. Sie können es nicht im iOS-Simulator testen.

Initialisieren und Konfigurieren des Video-Capturers

Die [OTKBasicVideoCapturerCamera initWithPreset: andDesiredFrameRate:] Methode ist ein Initialisierer für die Klasse OTKBasicVideoCapturerCamera. Sie ruft die sizeFromAVCapturePreset um die Auflösung des Bildes festzulegen. Auch die Bildgröße und die Bildrate werden hier eingestellt. Für die Erfassung von Bildern wird eine separate Warteschlange erstellt, um die UI-Warteschlange nicht zu beeinträchtigen.

- (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;
}

Die sizeFromAVCapturePreset Methode identifiziert den String-Wert der Bildauflösung im iOS AVFoundation Framework und gibt eine CGSize-Darstellung zurück.

Die Umsetzung der [OTVideoCapture initCapture] Methode verwendet das AVFoundation-Framework, um die Kamera auf die Aufnahme von Bildern einzustellen. Im ersten Teil der Methode wird eine Instanz von AVCaptureVideoDataOutput verwendet, um Bildrahmen zu erzeugen:

- (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 ...
}

Auf die mit dieser Methode erfassten Bilder wird mit der Methode [AVCaptureVideoDataOutputSampleBufferDelegate captureOutput:didOutputSampleBuffer:fromConnection:] Delegat-Methode. Das AVCaptureDevice-Objekt repräsentiert die Kamera und ihre Eigenschaften. Es liefert die aufgenommenen Bilder an ein AVCaptureSession-Objekt.

Der zweite Teil der initCapture Methode ruft die bestFrameRateForDevice Methode, um die beste Bildrate für die Bildaufnahme zu erhalten:

- (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];
}

Die [self bestFrameRateForDevice] Methode gibt die beste Bildrate für das Aufnahmegerät zurück:

- (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;
}

Das AVFoundation-Framework benötigt einen minimalen und maximalen Bereich von Bildraten, um die Qualität einer Bildaufnahme zu optimieren. Dieser Bereich wird in der bestFrameRate Objekt. Der Einfachheit halber sind die minimale und die maximale Bildrate auf dieselbe Zahl eingestellt, aber vielleicht möchten Sie Ihre eigenen minimalen und maximalen Bildraten festlegen, um eine bessere Bildqualität zu erhalten, die von der Geschwindigkeit Ihres Netzwerks abhängt. In dieser Anwendung sind die Bildrate und die Auflösung fest eingestellt.

Mit dieser Methode wird der durch das OTVideoCaptureConsumer-Protokoll definierte Videoaufnahmekonsument festgelegt.

- (void)setVideoCaptureConsumer:(id<OTVideoCaptureConsumer>)videoCaptureConsumer
{
    self.consumer = videoCaptureConsumer;
}

Die [OTVideoCapture captureSettings] Methode legt das Pixelformat und die Größe des vom Video-Capturer verwendeten Bildes fest, indem Eigenschaften des OTVideoFormat-Objekts gesetzt werden.

Die [OTVideoCapture currentDeviceOrientation] Die Methode ermittelt die Ausrichtung des Bildes im AVFoundation-Framework und gibt die entsprechende Wert aus, der durch die Enumeration „OTVideoOrientation“ im OpenTok iOS SDK definiert ist.

Aufnahme von Bildern für das Video des Herausgebers

Die Umsetzung der [OTVideoCapture startCapture] Methode wird aufgerufen, wenn der Herausgeber mit der Aufnahme von Videos zur Veröffentlichung beginnt. Sie ruft die [AVCaptureSession startRunning] Methode des AVCaptureSession-Objekts:

- (int32_t)startCapture
{
    self.captureStarted = YES;
    [self.captureSession startRunning];

    return 0;
}

Die [AVCaptureVideoDataOutputSampleBufferDelegate captureOutput:didOutputSampleBuffer:fromConnection:] Delegate-Methode wird aufgerufen, wenn ein neues Videobild von der Kamera verfügbar ist.

- (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);
}

Diese Methode bewirkt Folgendes:

  • Erzeugt eine OTVideoFrame-Instanz, um das neue Videobild zu definieren.

  • Speichert einen Bildpuffer in Abhängigkeit von der Größe des Bildes.

  • Schreibt die Bilddaten von zwei Ebenen in einen Speicherpuffer. Da es sich bei dem Bild um ein NV12 handelt, sind seine Daten auf zwei Ebenen verteilt. Es gibt eine Ebene für Y-Daten und eine Ebene für UV-Daten. Eine for-Schleife wird ausgeführt, um die beiden Ebenen zu durchlaufen und ihre Daten in einen Speicherpuffer zu schreiben.

  • Erzeugt einen Zeitstempel, um ein aufgenommenes Bild zu kennzeichnen. Jedes Bild wird mit einem Zeitstempel versehen, so dass Herausgeber und Abonnent die gleiche Zeitleiste erstellen und die Bilder in der gleichen Reihenfolge referenzieren können.

  • Ruft die [OTVideoCaptureConsumer consumeFrame:] Methode und übergibt dabei das OTVideoFrame-Objekt. Dies veranlasst den Herausgeber, das Bild in dem von ihm veröffentlichten Stream zu senden.

Die Umsetzung der [AVCaptureVideoDataOutputSampleBufferDelegate captureOutput:didDropSampleBuffer:fromConnection] wird immer dann aufgerufen, wenn es eine Verzögerung beim Empfang von Frames gibt. Sie verwirft Frames, um die Veröffentlichung in der Sitzung nicht zu unterbrechen:

- (void)captureOutput:(AVCaptureOutput *)captureOutput
  didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection
{
    NSLog(@"Frame dropped");
}

Weitere Hinweise zur App

Das OTVideoCapture-Protokoll enthält weitere erforderliche Methoden, die von der Klasse „OTKBasicVideoCapturerCamera“ implementiert werden. Da dieses Beispiel jedoch in diesen Methoden keine nennenswerten Funktionen ausführt, werden sie in dieser Erläuterung nicht behandelt.

Herzlichen Glückwunsch! Sie haben das Tutorial zur benutzerdefinierten Videoaufnahme unter iOS abgeschlossen.
Du kannst den Code, den du hier entwickelt hast, weiter ausprobieren und anpassen oder dir die Nächste Schritte unten.

Nächste Schritte

Wenn Sie hier fertig sind, können Sie Ihre OpenTok-Application mit diesen hilfreichen Ressourcen weiter ausbauen und verbessern: