Подтвердить что ты не робот

AVCaptureSession с несколькими превью

У меня есть AVCaptureSession, работающий с AVCaptureVideoPreviewLayer.

Я вижу видео, поэтому я знаю, что он работает.

Тем не менее, я хотел бы иметь представление коллекции и в каждой ячейке добавить слой предварительного просмотра, чтобы каждая ячейка показывала предварительный просмотр видео.

Если я попытаюсь передать слой предварительного просмотра в ячейку и добавить его в качестве субслоя, он удалит слой из других ячеек, чтобы он отображался только в одной ячейке за раз.

Есть ли другой (лучший) способ сделать это?

4b9b3361

Ответ 1

Я столкнулся с той же проблемой, что и одновременное отображение нескольких живых просмотров. Ответ использования UIImage выше был слишком медленным для того, что мне было нужно. Вот два найденных мной решения:

1. CAReplicatorLayer

Первый вариант - использовать CAReplicatorLayer, чтобы автоматически дублировать слой. Как говорят документы, он автоматически создаст "... определенное количество копий своих подслоев (исходный слой), каждая из которых имеет потенциально имеющие геометрические, временные и цветовые преобразования".

Это супер полезно, если нет большого взаимодействия с живыми превью, кроме простых геометрических или цветовых преобразований (Think Photo Booth). Я чаще всего видел, как CAReplicatorLayer используется для создания эффекта "отражения".

Вот пример кода для репликации 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)];

Инициализировать слой CAReplicatorLayer и установить свойства

Примечание. Это позволит реплицировать слой предварительного просмотра в реальном времени четыре.

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

Добавить слои

Примечание. Из моего опыта вам нужно добавить слой, который вы хотите реплицировать, в CAReplicatorLayer в качестве подуровня.

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

Downsides

Недостатком использования CAReplicatorLayer является то, что он обрабатывает все размещение реплик уровня. Поэтому он будет применять любые преобразования множеств для каждого экземпляра, и все это будет содержаться внутри себя. Например. Не было бы возможности репликации AVCaptureVideoPreviewLayer на две отдельные ячейки.


2. Ручное рендеринг SampleBuffer

Этот метод, хотя и более сложный, решает вышеупомянутый недостаток CAReplicatorLayer. Ручным рендерингом предварительного просмотра в реальном времени вы можете отображать столько просмотров, сколько хотите. Конечно, производительность может быть затронута.

Примечание. Могут быть другие способы визуализации SampleBuffer, но я выбрал OpenGL из-за его производительности. Код был вдохновлен и изменен с CIFunHouse.

Вот как я его реализовал:

2.1 Контексты и сеанс

Настройка контекста OpenGL и 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]}];

Очередь отправки

Эта очередь будет использоваться для сеанса и делегирования.

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

Инициируйте AVSession и AVCaptureVideoDataOutput

Примечание. Я удалил все проверки возможностей устройства, чтобы сделать это более читаемым.

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;
    :

Примечание. Следующий код - это "волшебный код". Именно здесь мы создаем и добавляем DataOutput в AVSession, чтобы мы могли перехватить кадры камеры с помощью делегата. Это прорыв, который мне нужен, чтобы выяснить, как решить проблему.

    :
    // 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 OpenGL Views

Мы используем GLKView для рендеринга наших живых превью. Поэтому, если вам нужно 4 просмотра в реальном времени, вам нужно 4 GLKView.

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

Поскольку собственное видеоизображение с задней камеры находится в UIDeviceOrientationLandscapeLeft (т.е. кнопка "домой" справа), нам нужно применить преобразование по часовой стрелке на 90 градусов, чтобы мы могли нарисовать предварительный просмотр видео, как если бы мы были в ландшафте -ориентированный вид; если вы используете переднюю камеру и хотите иметь зеркальный превью (чтобы пользователь видел себя в зеркале), вам нужно применить дополнительный горизонтальный флип (путем объединения CGAffineTransformMakeScale (-1.0, 1.0) в поворот преобразование)

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

Связать буфер кадра, чтобы получить ширину и высоту буфера кадра. Границы, используемые CIContext при рисовании в GLKView, находятся в пикселях (а не в точках), следовательно, необходимо читать из ширины и высоты буфера кадра.

[self.livePreviewView bindDrawable];

Кроме того, поскольку мы будем получать доступ к границам в другой очереди (_captureSessionQueue), мы хотим получить эту информацию, чтобы мы не получали доступ к свойствам _videoPreviewView из другого потока/очереди.

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

Примечание. Если вы используете фронтальную камеру, вы можете горизонтально перевернуть предварительный просмотр в прямом эфире следующим образом:

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

2.3 Выполнение делегирования

После того, как мы установили контексты, сеансы и GLKViews, мы теперь можем отображать наши представления из метода AVCaptureVideoDataOutputSampleBufferDelegate 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;

Вам понадобится ссылка на каждый GLKView, а также на videoPreviewViewBounds. Для легкости я предполагаю, что они оба содержатся в UICollectionViewCell. Вам нужно будет изменить это для своего собственного использования.

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

Это решение позволяет вам иметь как можно больше предварительных просмотров в реальном времени, как вы хотите, используя OpenGL для визуализации буфера изображений, полученных из AVCaptureVideoDataOutputSampleBufferDelegate.

3. Пример кода

Вот проект github, который я бросил вместе с обоими душами: https://github.com/JohnnySlagle/Multiple-Camera-Feeds

Ответ 2

внедрить метод AVCaptureSession delegate, который

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

используя это, вы можете получить выходной буфер выборки для каждого видеокадра. Используя вывод буфера, вы можете создать изображение, используя метод ниже.

- (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer 
{
    // Get a CMSampleBuffer 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);
}

чтобы вы могли добавить несколько изображений в ваше представление и добавить эти строки внутри метода делегата, о котором я упомянул ранее:

UIImage *image = [self imageFromSampleBuffer:sampleBuffer];
imageViewOne.image = image;
imageViewTwo.image = image;

Ответ 3

Просто установите содержимое слоя предварительного просмотра на другой CALayer:

CGImageRef cgImage = (__bridge CGImage) self.previewLayer.contents; self.duplicateLayer.contents = (__bridge id) cgImage;

Вы можете сделать это с содержимым любого слоя Metal или OpenGL. На моем конце не было увеличения использования памяти или загрузки процессора. Вы не дублируете ничего, кроме крошечного указателя. Это не так с этими другими "решениями".

У меня есть пример проекта, который вы можете загрузить, который отображает 20 уровней предварительного просмотра одновременно с одного канала камеры. Каждый слой имеет другой эффект, применяемый к нашему.

Вы можете посмотреть видеоролик приложения, а также загрузить исходный код по адресу:

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

Ответ 4

У вас не может быть нескольких предварительных просмотров. Только один выходной поток, как говорит Apple AVFundation. Я пробовал много способов, но вы просто не можете.