如何同时使用iPhone所有后置摄像头扫描条形码?
如何实现iPhone所有后置摄像头同时扫描条形码?
需求背景
希望同时使用iPhone的所有后置摄像头扫描条形码,以此降低用户对条形码距离的关注度。
已尝试的方案及问题
方案1:为每个摄像头创建独立AVCaptureSession
为每个后置摄像头单独创建AVCaptureSession,仅用其中一个摄像头的videoPreviewLayer展示画面,但启动多个Session后实时画面会冻结。
代码实现:
func setupCaptureSessions() { let devices = [ AVCaptureDevice.DiscoverySession(deviceTypes: codeScanningDevicesTypes, mediaType: .video, position: .back).devices.first!, AVCaptureDevice.DiscoverySession(deviceTypes: codeScanningDevicesTypes, mediaType: .video, position: .back).devices.last! ] for device in devices { let captureSession = AVCaptureSession() captureSession.beginConfiguration() guard let deviceInput = try? AVCaptureDeviceInput(device: device) else { print("Failed to create device input for camera") return } guard captureSession.canAddInput(deviceInput) else { print("Failed to add device input to capture session") return } captureSession.addInput(deviceInput) let metadataOutput = AVCaptureMetadataOutput() if captureSession.canAddOutput(metadataOutput) { captureSession.addOutput(metadataOutput) metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) metadataOutput.metadataObjectTypes = metadataOutput.availableMetadataObjectTypes } captureSession.commitConfiguration() captureSessions.append(captureSession) } } func startScanning() { // Video Konfiguration let videoOutput = AVCaptureVideoDataOutput() videoOutput.alwaysDiscardsLateVideoFrames = true guard let captureSession = captureSessions.first else { return } if captureSession.canAddOutput(videoOutput) { videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "video_ouput_queue")) captureSession.addOutput(videoOutput) } videoPreviewLayer.session = captureSession videoPreviewLayer.videoGravity = .resizeAspectFill videoPreviewLayer.connection?.videoOrientation = videoOrientationFromCurrentDeviceOrientation() for captureSession in captureSessions { captureSession.startRunning() } }
问题:多个AVCaptureSession同时运行时,预览画面冻结,系统资源占用过高且无法稳定工作。
方案2:使用AVCaptureMultiCamSession
尝试用AVCaptureMultiCamSession添加多个后置摄像头输入,但最终只有一个摄像头能真正识别条形码。
代码实现:
let codeScanningDevicesTypes: [AVCaptureDevice.DeviceType] = [ .builtInUltraWideCamera, .builtInDualWideCamera, .builtInWideAngleCamera, .builtInTripleCamera, .builtInDualCamera, .builtInTelephotoCamera, ] private let allDetactableCodeTypes = [ AVMetadataObject.ObjectType.code39, .code39Mod43, .code93, .code128, .ean8, .ean13, .interleaved2of5, .itf14, .upce, .aztec, .dataMatrix, .pdf417, .qr ] private func setupCaptureSession() { defer { captureDevice?.unlockForConfiguration() captureSession.commitConfiguration() } guard let device = captureDevice else { return } let backCameras:[AVCaptureDevice] = AVCaptureDevice.DiscoverySession(deviceTypes: codeScanningDevicesTypes, mediaType: .video, position: .back).devices captureSession.beginConfiguration() // Inputs: backCameras.forEach { device in guard let deviceInput = try? AVCaptureDeviceInput(device: device) else { return } if captureSession.canAddInput(deviceInput) { captureSession.addInput(deviceInput) } do { try configureDevice(device: device) } catch { let error = ImageScannerControllerError.inputDevice delegate?.captureSessionManager(self, didFailWithError: error) return } } // Outputs: let metadataOutput = AVCaptureMetadataOutput() if captureSession.canAddOutput(metadataOutput) { captureSession.addOutput(metadataOutput) metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) metadataOutput.metadataObjectTypes = allDetactableCodeTypes } let videoOutput = AVCaptureVideoDataOutput() videoOutput.alwaysDiscardsLateVideoFrames = true if captureSession.canAddOutput(videoOutput) { videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "video_ouput_queue")) captureSession.addOutput(videoOutput) } videoPreviewLayer.session = captureSession videoPreviewLayer.videoGravity = .resizeAspectFill videoPreviewLayer.connection?.videoOrientation = videoOrientationFromCurrentDeviceOrientation() }
问题:AVCaptureMetadataOutput仅会处理来自其中一个摄像头的数据流,无法同时对多摄像头画面进行条形码识别。
解决方案
核心思路
AVCaptureMultiCamSession是正确方向,但需要为每个摄像头输入单独关联对应的输出(包括AVCaptureMetadataOutput和AVCaptureVideoDataOutput),并通过输出的connection绑定到特定摄像头输入,确保每个摄像头的画面都被单独处理。
步骤1:初始化AVCaptureMultiCamSession
确保你的captureSession是AVCaptureMultiCamSession类型:
private let captureSession = AVCaptureMultiCamSession()
步骤2:为每个摄像头输入创建独立的Metadata输出
遍历所有后置摄像头,为每个输入添加专属的AVCaptureMetadataOutput,并绑定到对应摄像头:
private func setupCaptureSession() { captureSession.beginConfiguration() defer { captureSession.commitConfiguration() } let backCameras = AVCaptureDevice.DiscoverySession( deviceTypes: codeScanningDevicesTypes, mediaType: .video, position: .back ).devices for camera in backCameras { // 添加摄像头输入 guard let cameraInput = try? AVCaptureDeviceInput(device: camera) else { print("无法创建摄像头输入: \(camera.localizedName)") continue } guard captureSession.canAddInput(cameraInput) else { print("无法添加摄像头输入到Session: \(camera.localizedName)") continue } captureSession.addInput(cameraInput) // 配置摄像头(自动对焦、曝光) do { try camera.lockForConfiguration() camera.focusMode = .continuousAutoFocus camera.exposureMode = .continuousAutoExposure camera.unlockForConfiguration() } catch { print("配置摄像头失败: \(camera.localizedName), 错误: \(error)") continue } // 为该摄像头创建专属的Metadata输出 let metadataOutput = AVCaptureMetadataOutput() guard captureSession.canAddOutput(metadataOutput) else { print("无法添加Metadata输出到Session: \(camera.localizedName)") continue } captureSession.addOutput(metadataOutput) // 绑定输出到当前摄像头输入 guard let connection = metadataOutput.connection(with: .metadata, inputPort: cameraInput.ports.first!) else { print("无法为摄像头创建Metadata连接: \(camera.localizedName)") continue } connection.isEnabled = true // 设置Metadata代理和识别类型 metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue(label: "metadata_queue_\(camera.uniqueID)")) metadataOutput.metadataObjectTypes = allDetactableCodeTypes } // 配置预览层(选择第一个摄像头作为预览画面) if let firstCameraInput = captureSession.inputs.first as? AVCaptureDeviceInput { let videoOutput = AVCaptureVideoDataOutput() if captureSession.canAddOutput(videoOutput) { captureSession.addOutput(videoOutput) if let previewConnection = videoOutput.connection(with: .video, inputPort: firstCameraInput.ports.first!) { videoPreviewLayer.session = captureSession videoPreviewLayer.connection = previewConnection videoPreviewLayer.videoGravity = .resizeAspectFill } videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "preview_video_queue")) } } }
步骤3:在代理方法中区分不同摄像头的识别结果
在AVCaptureMetadataOutputObjectsDelegate回调中,通过connection关联到对应摄像头,判断识别来源:
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { guard let inputPort = connection.inputPort else { return } guard let cameraInput = captureSession.inputs.first(where: { ($0 as? AVCaptureDeviceInput)?.ports.contains(inputPort) ?? false }) as? AVCaptureDeviceInput else { return } let cameraName = cameraInput.device.localizedName print("摄像头 \(cameraName) 识别到条形码:") for metadata in metadataObjects { guard let codeObject = metadata as? AVMetadataMachineReadableCodeObject, let codeValue = codeObject.stringValue else { continue } print("- \(codeObject.type.rawValue): \(codeValue)") // 这里添加条形码识别后的业务逻辑 } }
关键注意事项
- 资源限制:同时运行多个摄像头会占用大量CPU和内存,建议只启用必要的后置摄像头(如超广角+广角+长焦),避免设备过热或卡顿。
- 预览画面:可选择单个摄像头画面预览,或通过
AVCaptureVideoDataOutput获取多摄像头帧数据手动合并预览(需图像处理能力)。 - 权限处理:确保已获取相机访问权限,否则无法启动摄像头。
- 兼容性:部分老款iPhone仅支持双摄同时工作,需在代码中做兼容性判断。
内容的提问来源于stack exchange,提问作者tuvok
相关产品推荐
相关产品推荐

