You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何同时使用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)")
        // 这里添加条形码识别后的业务逻辑
    }
}

关键注意事项

  1. 资源限制:同时运行多个摄像头会占用大量CPU和内存,建议只启用必要的后置摄像头(如超广角+广角+长焦),避免设备过热或卡顿。
  2. 预览画面:可选择单个摄像头画面预览,或通过AVCaptureVideoDataOutput获取多摄像头帧数据手动合并预览(需图像处理能力)。
  3. 权限处理:确保已获取相机访问权限,否则无法启动摄像头。
  4. 兼容性:部分老款iPhone仅支持双摄同时工作,需在代码中做兼容性判断。

内容的提问来源于stack exchange,提问作者tuvok

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 01:07:03