具有多个预览的AVCaptureSession
我有一个AVCaptureSession与AVCaptureVideoPreviewLayer运行。
我可以看到video,所以我知道它的工作。
但是,我想要一个集合视图,并在每个单元格中添加一个预览图层,以便每个单元格显示video的预览。
如果我尝试将预览图层传递到单元格中并将其添加为子图层,则会将该图层从其他单元格中移除,因此一次只能显示在一个单元格中。
是否有另一种(更好)的方式来做到这一点?
我遇到了同时需要显示多个实时视图的问题。 上面使用UIImage的答案对于我所需要的太慢了。 以下是我find的两个解决scheme:
CAReplicatorLayer
第一个选项是使用CAReplicatorLayer自动复制图层。 正如文档所说,它会自动创build“…其子层(源图层)的指定数量的副本,每个副本可能会应用几何,时间和颜色转换。
如果除了简单的几何或颜色转换(Think Photo Booth)之外,没有太多与实况预览的交互,这是非常有用的。 我经常看到CAReplicatorLayer被用来创build“reflection”效果。
以下是一些复制CACaptureVideoPreviewLayer的示例代码:
初始化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];
缺点
使用CAReplicatorLayer的一个缺点是它处理层复制的所有位置。 所以它会将任何设置的转换应用到每个实例,并且它将全部包含在其内部。 例如,在两个单独的单元格上不可能有AVCaptureVideoPreviewLayer的复制。
2.手动渲染SampleBuffer
这种方法虽然稍微复杂一些,但解决了上述的CAReplicatorLayer的缺点。 通过手动渲染实时预览,您可以渲染尽可能多的视图。 当然,性能可能会受到影响。
注意:可能还有其他方法来渲染SampleBuffer,但是我selectOpenGL是因为它的性能。 代码是从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
注意:我已经删除了所有设备function检查,以使其更具可读性。
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; :
注意:以下代码是“魔术代码”。 这是我们创build的地方,并将一个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视图
我们正在使用GLKView来呈现我们的实时预览。 所以如果你想要4个实时预览,那么你需要4个GLKView。
self.livePreviewView = [[GLKView alloc] initWithFrame:self.bounds context:self.eaglContext]; self.livePreviewView = NO;
由于来自后置摄像头的原始video图像位于UIDeviceOrientationLandscapeLeft(即主页button位于右侧),因此我们需要应用顺时针90度变换,以便我们可以绘制video预览,就好像我们处于横向视图; 如果您使用的是前置摄像头,并且您希望进行镜像预览(以便用户看到自己在镜像中),则需要应用额外的水平翻转(通过将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代表实施
在设置了上下文,会话和GLKView之后,我们现在可以通过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]; } }
这个解决scheme可以让您拥有尽可能多的实时预览,您可以使用OpenGL渲染从AVCaptureVideoDataOutputSampleBufferDelegate接收到的图像的缓冲区。
3.示例代码
这是一个github项目,我把这两个soultions一起扔: https : //github.com/JohnnySlagle/Multiple-Camera-Feeds
实现这个AVCaptureSession委托方法
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
使用这个你可以得到每个video帧的采样缓冲区输出。 使用缓冲区输出,您可以使用下面的方法创build一个图像。
- (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); }
所以你可以添加几个imageViews到你的视图,并在我之前提到的委托方法中添加这些行:
UIImage *image = [self imageFromSampleBuffer:sampleBuffer]; imageViewOne.image = image; imageViewTwo.image = image;
你不能有多个预览。 Apple AVFundation说只有一个输出stream。 我尝试了很多方法,但是你却做不到。
- AVURLAsset拒绝加载video
- 如何解决警告:发送'ViewController * const __strong'参数的不兼容types'id <AVAudioPlayerDelegate>?
- 如何在IOS中做慢动作video
- AVPlayerItem失败,AVStatusFailed和错误代码“无法解码”
- ios使用AVFramework捕获图像
- AVFoundation,如何captureStillImageAsynchronouslyFromConnectionclosures快门声音?
- AVFoundation + AssetWriter:通过图像和audio生成电影
- AVAssetImageGenerator提供旋转的图像
- 使用AVFoundation AVPlayer播放video?