我正在尝试使用 AVFoundation、GLKit 和 Core Image(不使用 @987654326 @)
所以,我找到了这个教程
http://altitudelabs.com/blog/real-time-filter/
它是用 Objective-C 编写的,所以我用 Swift4.0、XCode9 重写了该代码
它看起来工作正常,但有时(很少)它会因以下错误而崩溃。当GLKView的display方法被调用时
EXC_BAD_ACCESS(代码=1,地址+0x********)
崩溃的时间,GLKView 存在(不是 nil),EAGLContext 存在,CIContext 存在。我的代码如下
导入 UIKit
导入 AVFoundation
导入 GLKit
导入 OpenGLES
类视图控制器:UIViewController {
var videoDevice : AVCaptureDevice!
var captureSession : AVCaptureSession!
var captureSessionQueue : DispatchQueue!
var videoPreviewView: GLKView!
var ciContext: CIContext!
var eaglContext: EAGLContext!
var videoPreviewViewBounds: CGRect = CGRect.zero
覆盖 func viewDidLoad() {
super.viewDidLoad()
// 在加载视图后做任何额外的设置,通常是从一个 nib。
// 移除视图的背景颜色;这允许我们不使用 opaque 属性(self.view.opaque = NO),因为我们完全删除了背景颜色绘图
self.view.backgroundColor = UIColor.clear
// 为视频/图像预览设置 GLKView
让窗口:UIView = UIApplication.shared.delegate!.window!!
eaglContext = EAGLContext(api: .openGLES2)
videoPreviewView = GLKView(帧:videoPreviewViewBounds,上下文:eaglContext)
videoPreviewView.enableSetNeedsDisplay = false
// 因为后置摄像头的原生视频图像在 UIDeviceOrientationLandscapeLeft 中(即主页按钮在右侧),我们需要应用顺时针 90 度变换,以便我们可以像在风景中一样绘制视频预览 -面向视图;如果您使用前置摄像头并且想要进行镜像预览(以便用户在镜子中看到自己),则需要应用额外的水平翻转(通过将 CGAffineTransformMakeScale(-1.0, 1.0) 连接到旋转转变)
videoPreviewView.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2.0)
videoPreviewView.frame = window.bounds
// 我们将视频预览视图设为窗口的子视图,并将其发送到后面;这使得 ViewController 的视图(及其 UI 元素)位于视频预览之上,并且还使视频预览不受设备旋转的影响
window.addSubview(videoPreviewView)
window.sendSubview(toBack: videoPreviewView)
// 绑定帧缓冲区,得到帧缓冲区的宽高;
// CIContext 在绘制到 GLKView 时使用的边界以像素(不是点)为单位,
// 因此需要从帧缓冲区的宽度和高度中读取;
// 此外,由于我们将访问另一个队列 (_captureSessionQueue) 中的边界,
// 我们想要获取这条信息,这样我们就不会
// 从另一个线程/队列访问 _videoPreviewView 的属性
videoPreviewView.bindDrawable()
videoPreviewViewBounds = CGRect.zero
videoPreviewViewBounds.size.width = CGFloat(videoPreviewView.drawableWidth)
videoPreviewViewBounds.size.height = CGFloat(videoPreviewView.drawableHeight)
// 创建 CIContext 实例,注意这必须在 _videoPreviewView 正确设置后完成
ciContext = CIContext(eaglContext: eaglContext, 选项: [kCIContextWorkingColorSpace: NSNull()])
如果 AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera, .builtInTelephotoCamera, .builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back).devices.count > 0 {
开始()
} 别的 {
print("没有带有 AVMediaTypeVideo 的设备")
}
}
覆盖 func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// 处理所有可以重新创建的资源。
}
函数开始(){
让 videoDevices = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back).devices
videoDevice = videoDevices.first
var videoDeviceInput : AVCaptureInput!
做 {
videoDeviceInput = 尝试 AVCaptureDeviceInput(设备:videoDevice)
} 捕捉让错误 {
print("无法获取视频设备输入,错误:\(error)")
返回
}
让预设 = AVCaptureSession.Preset.high
captureSession = AVCaptureSession()
captureSession.sessionPreset = 预设
// 核心图像 watns bgra 像素格式
让 outputSetting = [String(kCVPixelBufferPixelFormatTypeKey): kCVPixelFormatType_32BGRA]
// 创建和配置视频数据输出
让 videoDataOutput = AVCaptureVideoDataOutput()
videoDataOutput.videoSettings = outputSetting
// 创建用于处理捕获会话委托方法调用的调度队列
captureSessionQueue = DispatchQueue(标签:“capture_session_queue”)
videoDataOutput.setSampleBufferDelegate(self, queue: captureSessionQueue)
videoDataOutput.alwaysDiscardsLateVideoFrames = true
captureSession.beginConfiguration()
如果 !captureSession.canAddOutput(videoDataOutput) {
print("不能添加视频数据输出")
捕获会话 = 无
返回
}
captureSession.addInput(videoDeviceInput)
captureSession.addOutput(videoDataOutput)
captureSession.commitConfiguration()
captureSession.startRunning()
}
}
扩展视图控制器:AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_输出:AVCaptureOutput,didOutput sampleBuffer:CMSampleBuffer,来自连接:AVCaptureConnection){
让 imageBuffer : CVImageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
让 sourceImage = CIImage(cvImageBuffer: imageBuffer, options: nil)
让 sourceExtent = sourceImage.extent
让 vignetteFilter = CIFilter(name: "CIVignetteEffect", withInputParameters: nil)
vignetteFilter?.setValue(sourceImage, forKey: kCIInputImageKey)
vignetteFilter?.setValue(CIVector(x: sourceExtent.size.width/2.0, y: sourceExtent.size.height/2.0), forKey: kCIInputCenterKey)
vignetteFilter?.setValue(sourceExtent.width/2.0, forKey: kCIInputRadiusKey)
让filteredImage = vignetteFilter?.outputImage
让 sourceAspect = sourceExtent.width/sourceExtent.height
让 previewAspect = videoPreviewViewBounds.width/videoPreviewViewBounds.height
// 我们要保持屏幕大小的宽高比,所以我们剪辑视频图像
var drawRect = sourceExtent
如果 sourceAspect > previewAspect {
// 使用视频图像的全高,并居中裁剪宽度
drawRect.origin.x += (drawRect.size.width - drawRect.size.height * previewAspect) / 2.0
drawRect.size.width = drawRect.size.height * previewAspect
} 别的 {
// 使用视频图像的全宽,并居中裁剪高度
drawRect.origin.y += (drawRect.size.height - drawRect.size.width / previewAspect) / 2.0;
drawRect.size.height = drawRect.size.width / previewAspect;
}
videoPreviewView.bindDrawable()
如果 eaglContext != EAGLContext.current() {
EAGLContext.setCurrent(eaglContext)
}
print("当前线程 \(Thread.current)")
// 清除老鹰视图为灰色
glClearColor(0.5, 0.5, 0.5, 1.0);
glClear(GLbitfield(GL_COLOR_BUFFER_BIT));
// 将混合模式设置为“source over”,以便 CI 使用它
glEnable(GLenum(GL_BLEND));
glBlendFunc(GLenum(GL_ONE), GLenum(GL_ONE_MINUS_SRC_ALPHA));
如果让过滤图像 = 过滤图像 {
ciContext.draw(filteredImage, in: videoPreviewViewBounds, from: drawRect)
}
videoPreviewView.display()
}
}
崩溃时的堆栈是
*线程#5,队列='com.apple.avfoundation.videodataoutput.bufferqueue',停止原因=EXC_BAD_ACCESS(代码=1,地址=0x8000000000000000)
帧#0:0x00000001a496f098 AGXGLDriver`___lldb_unnamed_symbol149$$AGXGLDriver + 332
帧 #1: 0x00000001923c029c OpenGLES`-[EAGLContext getParameter:to:] + 80
帧#2:0x000000010038bca4 libglInterpose.dylib`__clang_call_terminate + 1976832
帧#3:0x00000001001ab75c libglInterpose.dylib`__clang_call_terminate + 9400
帧#4:0x000000010038b8b4 libglInterpose.dylib`__clang_call_terminate + 1975824
帧#5:0x00000001001af098 libglInterpose.dylib`__clang_call_terminate + 24052
帧 #6:0x00000001001abe5c libglInterpose.dylib`__clang_call_terminate + 11192
帧#7:0x000000010038f9dc libglInterpose.dylib`__clang_call_terminate + 1992504
帧#8:0x000000010038d5b8 libglInterpose.dylib`__clang_call_terminate + 1983252
第 9 帧:0x000000019a1e2a20 GLKit`-[GLKView _display:] + 308
* 帧#10:0x0000000100065e78 RealTimeCameraPractice`ViewController.captureOutput(输出=0x0000000174034820,sampleBuffer=0x0000000119e25e70,连接=0x0000000174008850,self=0x0000000119d032d0:在ViewController.61)
帧 #11: 0x00000001000662dc RealTimeCameraPractice`@objc ViewController.captureOutput(_:didOutput:from:) at ViewController.swift:0
第 12 帧:0x00000001977ec310 AVFoundation`-[AVCaptureVideoDataOutput _handleRemoteQueueOperation:] + 308
第 13 帧:0x00000001977ec14c AVFoundation`__47-[AVCaptureVideoDataOutput _updateRemoteQueue:]_block_invoke + 100
帧#14:0x00000001926bdf38 CoreMedia`__FigRemoteOperationReceiverCreateMessageReceiver_block_invoke + 260
帧 #15:0x00000001926dce9c CoreMedia`__FigRemoteQueueReceiverSetHandler_block_invoke.2 + 224
帧 #16:0x000000010111da10 libdispatch.dylib`_dispatch_client_callout + 16
帧 #17:0x0000000101129a84 libdispatch.dylib`_dispatch_continuation_pop + 552
帧 #18:0x00000001011381f8 libdispatch.dylib`_dispatch_source_latch_and_call + 204
帧 #19:0x000000010111fa60 libdispatch.dylib`_dispatch_source_invoke + 828
帧 #20:0x000000010112b128 libdispatch.dylib`_dispatch_queue_serial_drain + 692
帧 #21:0x0000000101121634 libdispatch.dylib`_dispatch_queue_invoke + 852
帧 #22:0x000000010112b128 libdispatch.dylib`_dispatch_queue_serial_drain + 692
帧 #23:0x0000000101121634 libdispatch.dylib`_dispatch_queue_invoke + 852
帧 #24:0x000000010112c358 libdispatch.dylib`_dispatch_root_queue_drain_deferred_item + 276
帧 #25:0x000000010113457c libdispatch.dylib`_dispatch_kevent_worker_thread + 764
帧 #26:0x000000018ee56fbc libsystem_pthread.dylib`_pthread_wqthread + 772
帧 #27: 0x000000018ee56cac libsystem_pthread.dylib`start_wqthread + 4
我的项目在github
https://github.com/hegrecom/iOS-RealTimeCameraPractice