Avcapturession z wieloma podglądami

Mam avcapturession uruchomiony z AVCaptureVideoPreviewLayer.

Widzę filmik, więc wiem, że działa.

Jednak chciałbym mieć widok kolekcji i w każdej komórce dodać warstwę podglądu, aby każda komórka pokazywała podgląd wideo.

Jeśli spróbuję przekazać warstwę podglądu do komórki i dodać ją jako podwarstwę, usunie ona warstwę z innych komórek, aby zawsze wyświetlała się tylko w jednej komórce na raz.

Czy jest inny (lepszy) sposób na robiąc to?

Author: Fogmeister, 2013-05-14

4 answers

Natknąłem się na ten sam problem konieczności wyświetlania wielu widoków na żywo w tym samym czasie. Odpowiedź korzystania z UIImage powyżej była zbyt wolna dla tego, czego potrzebowałem. Oto dwa rozwiązania, które znalazłem:

1. CAReplicatorLayer

Pierwszą opcją jest użycie CAReplicatorLayer aby automatycznie powielić warstwę. Jak mówią dokumenty, automatycznie utworzy "...określoną liczbę kopii jej podwarstw (warstwy źródłowej), każda kopia może mieć zastosowano w nim przekształcenia geometryczne, czasowe i barwne."

Jest to bardzo przydatne, jeśli nie ma zbyt wielu interakcji z podglądami na żywo poza prostymi transformacjami geometrycznymi lub kolorowymi (pomyśl o Fotobudce). Najczęściej widziałem CAReplicatorLayer używany jako sposób na stworzenie efektu "odbicia".

Oto przykładowy kod do replikacji CACaptureVideoPreviewLayer:

Init AVCaptureVideoPreviewLayer

AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
[previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[previewLayer setFrame:CGRectMake(0.0, 0.0, self.view.bounds.size.width, self.view.bounds.size.height / 4)];

Init CAReplicatorLayer I set właściwości

Uwaga: spowoduje to powtórzenie warstwy podglądu na żywo cztery razy .

NSUInteger replicatorInstances = 4;

CAReplicatorLayer *replicatorLayer = [CAReplicatorLayer layer];
replicatorLayer.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height / replicatorInstances);
replicatorLayer.instanceCount = instances;
replicatorLayer.instanceTransform = CATransform3DMakeTranslation(0.0, self.view.bounds.size.height / replicatorInstances, 0.0);

Dodaj Warstwy

Uwaga: Z mojego doświadczenia musisz dodać warstwę, którą chcesz replikować do CAReplicatorLayer jako podwarstwę.

[replicatorLayer addSublayer:previewLayer];
[self.view.layer addSublayer:replicatorLayer];

Downsides

Minusem korzystania z CAReplicatorLayer jest to, że obsługuje wszystkie rozmieszczenie replikacji warstw. Zastosuje więc wszelkie przekształcenia zestawu do każdej instancji i będzie to wszystko być zawarte w sobie. np. nie ma możliwości replikacji AVCaptureVideoPreviewLayer na dwóch oddzielnych komórkach.


2. Ręczne Renderowanie SampleBuffer

Ta metoda, choć nieco bardziej złożona, rozwiązuje wyżej wymienione wady CAReplicatorLayer. Ręczne renderowanie podglądów na żywo umożliwia renderowanie dowolnej liczby widoków. Przyznaję, może to mieć wpływ na wydajność.

Uwaga: Może być inne sposoby renderowania Samplebuffera, ale wybrałem OpenGL ze względu na jego wydajność. Kod został zainspirowany i zmieniony z CIFunHouse.

Oto Jak to zaimplementowałem:

2.1 konteksty i sesja

Konfiguracja OpenGL i kontekstu CoreImage

_eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

// Note: must be done after the all your GLKViews are properly set up
_ciContext = [CIContext contextWithEAGLContext:_eaglContext
                                       options:@{kCIContextWorkingColorSpace : [NSNull null]}];

Kolejka Wysyłkowa

Ta kolejka będzie używana dla sesji i delegata.

self.captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL);

Init your AVSession & AVCaptureVideoDataOutput

Uwaga: usunąłem wszystkie urządzenia sprawdzanie zdolności, aby było to bardziej czytelne.

dispatch_async(self.captureSessionQueue, ^(void) {
    NSError *error = nil;

    // get the input device and also validate the settings
    NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];

    AVCaptureDevice *_videoDevice = nil;
    if (!_videoDevice) {
        _videoDevice = [videoDevices objectAtIndex:0];
    }

    // obtain device input
    AVCaptureDeviceInput *videoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:self.videoDevice error:&error];

    // obtain the preset and validate the preset
    NSString *preset = AVCaptureSessionPresetMedium;

    // CoreImage wants BGRA pixel format
    NSDictionary *outputSettings = @{(id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA)};

    // create the capture session
    self.captureSession = [[AVCaptureSession alloc] init];
    self.captureSession.sessionPreset = preset;
    :

Uwaga: poniższy kod to "magiczny kod". W tym miejscu tworzymy i dodajemy DataOutput do AVSession, dzięki czemu możemy przechwycić klatki aparatu za pomocą delegata. To jest przełom, którego potrzebowałem, aby dowiedzieć się, jak rozwiązać problem.

    :
    // create and configure video data output
    AVCaptureVideoDataOutput *videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
    videoDataOutput.videoSettings = outputSettings;
    [videoDataOutput setSampleBufferDelegate:self queue:self.captureSessionQueue];

    // begin configure capture session
    [self.captureSession beginConfiguration];

    // connect the video device input and video data and still image outputs
    [self.captureSession addInput:videoDeviceInput];
    [self.captureSession addOutput:videoDataOutput];

    [self.captureSession commitConfiguration];

    // then start everything
    [self.captureSession startRunning];
});

2.2 Widoki OpenGL

Używamy GLKView do renderowania naszych podglądów na żywo. Więc jeśli chcesz 4 podglądów na żywo, to potrzebujesz 4 GLKView.

self.livePreviewView = [[GLKView alloc] initWithFrame:self.bounds context:self.eaglContext];
self.livePreviewView = NO;

Ponieważ natywny obraz wideo Z Tylnej Kamery znajduje się w UIDeviceOrientationLandscapeLeft (tzn. przycisk home znajduje się po prawej stronie), musimy zastosować transformację 90 stopni zgodnie z ruchem wskazówek zegara, abyśmy mogli narysować podgląd wideo tak, jakbyśmy byli w widoku poziomym; jeśli używasz przedniej kamery i chcesz mieć podgląd lustrzany (tak, że użytkownik widzi siebie w lustrze), musisz zastosować dodatkowe odwrócenie w poziomie (poprzez połączenie CGAffineTransformMakeScale (-1.0, 1.0) do transformacji rotacyjnej)

self.livePreviewView.transform = CGAffineTransformMakeRotation(M_PI_2);
self.livePreviewView.frame = self.bounds;    
[self addSubview: self.livePreviewView];

Zwiąż bufor ramki, aby uzyskać szerokość i wysokość bufora ramki. Granice używane przez CIContext podczas rysowania do GLKView są w pikselach (nie punktach), stąd potrzeba odczytu z bufora ramki szerokości i wysokości.

[self.livePreviewView bindDrawable];

Ponadto, ponieważ będziemy uzyskiwać dostęp do granic w innej kolejce (_captureSessionQueue), chcemy uzyskać tę informację, aby nie uzyskać dostępu Właściwości _videoPreviewView z innego wątku/kolejki.

_videoPreviewViewBounds = CGRectZero;
_videoPreviewViewBounds.size.width = _videoPreviewView.drawableWidth;
_videoPreviewViewBounds.size.height = _videoPreviewView.drawableHeight;

dispatch_async(dispatch_get_main_queue(), ^(void) {
    CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_2);        

    // *Horizontally flip here, if using front camera.*

    self.livePreviewView.transform = transform;
    self.livePreviewView.frame = self.bounds;
});

Uwaga: Jeśli używasz przedniej kamery, możesz obracać podgląd na żywo w poziomie w następujący sposób:

transform = CGAffineTransformConcat(transform, CGAffineTransformMakeScale(-1.0, 1.0));

2.3 Implementacja Delegatów

Po skonfigurowaniu kontekstów, sesji i GLKViews możemy teraz renderować nasze widoki z AVCaptureVideoDataOutputSampleBufferdelegate metoda captureOutput:didOutputSampleBuffer:fromConnection:

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    CMFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer);

    // update the video dimensions information
    self.currentVideoDimensions = CMVideoFormatDescriptionGetDimensions(formatDesc);

    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    CIImage *sourceImage = [CIImage imageWithCVPixelBuffer:(CVPixelBufferRef)imageBuffer options:nil];

    CGRect sourceExtent = sourceImage.extent;
    CGFloat sourceAspect = sourceExtent.size.width / sourceExtent.size.height;

Będziesz musiał mieć odniesienie do każdego GLKView i to videoPreviewViewBounds. Dla ułatwienia zakładam, że oba są zawarte w UICollectionViewCell. Będziesz musiał zmienić to na własny użytek.

    for(CustomLivePreviewCell *cell in self.livePreviewCells) {
        CGFloat previewAspect = cell.videoPreviewViewBounds.size.width  / cell.videoPreviewViewBounds.size.height;

        // To maintain the aspect radio of the screen size, we clip the video image
        CGRect drawRect = sourceExtent;
        if (sourceAspect > previewAspect) {
            // use full height of the video image, and center crop the width
            drawRect.origin.x += (drawRect.size.width - drawRect.size.height * previewAspect) / 2.0;
            drawRect.size.width = drawRect.size.height * previewAspect;
        } else {
            // use full width of the video image, and center crop the height
            drawRect.origin.y += (drawRect.size.height - drawRect.size.width / previewAspect) / 2.0;
            drawRect.size.height = drawRect.size.width / previewAspect;
        }

        [cell.livePreviewView bindDrawable];

        if (_eaglContext != [EAGLContext currentContext]) {
            [EAGLContext setCurrentContext:_eaglContext];
        }

        // clear eagl view to grey
        glClearColor(0.5, 0.5, 0.5, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);

        // set the blend mode to "source over" so that CI will use that
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

        if (sourceImage) {
            [_ciContext drawImage:sourceImage inRect:cell.videoPreviewViewBounds fromRect:drawRect];
        }

        [cell.livePreviewView display];
    }
}

To rozwiązanie pozwala mieć tyle podglądów na żywo, ile chcesz, używając OpenGL do renderowania bufora obrazów otrzymanych z AVCaptureVideoDataOutputSampleBufferdelegate.

3. Przykładowy Kod

Oto projekt Githuba, który wrzuciłem razem z obydwoma soultionami: https://github.com/JohnnySlagle/Multiple-Camera-Feeds

 55
Author: Johnny,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-06 18:31:32

Zaimplementuj metodę avcapturession delegate , która jest

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection

Za pomocą tego można uzyskać wyjście bufora próbki dla każdej klatki wideo. Za pomocą wyjścia bufora można utworzyć obraz za pomocą poniższej metody.

- (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer 
{
    // Get a CMSampleBuffer's Core Video image buffer for the media data
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 
    // Lock the base address of the pixel buffer
    CVPixelBufferLockBaseAddress(imageBuffer, 0); 

    // Get the number of bytes per row for the pixel buffer
    void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer); 

    // Get the number of bytes per row for the pixel buffer
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); 
    // Get the pixel buffer width and height
    size_t width = CVPixelBufferGetWidth(imageBuffer); 
    size_t height = CVPixelBufferGetHeight(imageBuffer); 

    // Create a device-dependent RGB color space
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 

    // Create a bitmap graphics context with the sample buffer data
    CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8, 
                                                 bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); 
    // Create a Quartz image from the pixel data in the bitmap graphics context
    CGImageRef quartzImage = CGBitmapContextCreateImage(context); 
    // Unlock the pixel buffer
    CVPixelBufferUnlockBaseAddress(imageBuffer,0);

    // Free up the context and color space
    CGContextRelease(context); 
    CGColorSpaceRelease(colorSpace);

    // Create an image object from the Quartz image
      UIImage *image = [UIImage imageWithCGImage:quartzImage scale:1.0 orientation:UIImageOrientationRight];

    // Release the Quartz image
    CGImageRelease(quartzImage);

    return (image);
}

Więc możesz dodać kilka obrazów do widoku i dodać te linie wewnątrz metody delegata, o której wspomniałem wcześniej:

UIImage *image = [self imageFromSampleBuffer:sampleBuffer];
imageViewOne.image = image;
imageViewTwo.image = image;
 8
Author: Ushan87,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-14 12:40:44

Wystarczy ustawić zawartość warstwy podglądu na inną warstwę:

CGImageRef cgImage = (__bridge CGImage)self.podglądacz.spis treści; siebie.duplicateLayer.contents = (__bridge id) cgImage;

Możesz to zrobić z zawartością dowolnej warstwy metalu lub OpenGL. Nie było też wzrostu zużycia pamięci ani obciążenia procesora po mojej stronie. Nie powielasz niczego poza małym wskaźnikiem. Tak nie jest z tymi innymi " rozwiązaniami."

Mam przykładowy projekt, który możesz Pobierz, że wyświetla 20 warstw podglądu w tym samym czasie z jednego obrazu kamery. Każda warstwa ma inny efekt nałożony na nasz.

Możesz obejrzeć film z uruchomionej aplikacji, a także pobrać kod źródłowy pod adresem:

Https://demonicactivity.blogspot.com/2017/05/developer-iphone-video-camera-wall.html?m=1

 1
Author: James Bush,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-27 09:42:23

Nie możesz mieć wielu podglądów. Tylko jeden strumień wyjściowy, jak mówi AVFundation Apple. Próbowałem na wiele sposobów, ale ty po prostu nie możesz.

 -1
Author: Shamanoid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-14 12:33:26