You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

iOS中是否存在类似Android onPreviewFrame的相机帧回调方法?

在iOS(Swift)中获取相机原始字节数组帧的方法

当然有啦!在iOS里,我们主要依靠AVFoundation框架来实现和Android Camera.PreviewCallback.onPreviewFrame类似的功能——通过代理回调获取相机的原始帧数据,再转换成字节数组。下面是具体的实现步骤和代码示例:

核心代理方法

对应Android的onPreviewFrame,iOS里的关键是实现AVCaptureVideoDataOutputSampleBufferDelegate协议中的captureOutput(_:didOutput:from:)方法。每当相机有新的预览帧生成时,这个方法就会被调用,我们可以在这里处理原始帧数据。

完整实现示例

1. 导入框架并声明代理

首先导入AVFoundation,然后让你的类遵循AVCaptureVideoDataOutputSampleBufferDelegate协议:

import AVFoundation

class CameraFrameCapture: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
    private var captureSession: AVCaptureSession!
    
    // 初始化相机会话
    func setupCamera() {
        captureSession = AVCaptureSession()
        captureSession.sessionPreset = .medium // 根据需求设置分辨率
        
        // 获取相机输入(这里以后置相机为例,前置相机用AVCaptureDevice.Position.front)
        guard let cameraDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
              let input = try? AVCaptureDeviceInput(device: cameraDevice) else {
            print("无法获取相机设备")
            return
        }
        
        // 添加输入到会话
        if captureSession.canAddInput(input) {
            captureSession.addInput(input)
        }
        
        // 创建视频输出并设置代理
        let videoOutput = AVCaptureVideoDataOutput()
        // 设置像素格式,这里用常用的YUV格式,和Android预览帧格式相近
        videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange]
        // 指定回调队列,建议用串行队列避免线程冲突
        let queue = DispatchQueue(label: "CameraFrameQueue")
        videoOutput.setSampleBufferDelegate(self, queue: queue)
        
        // 添加输出到会话
        if captureSession.canAddOutput(videoOutput) {
            captureSession.addOutput(videoOutput)
        }
        
        // 启动会话
        captureSession.startRunning()
    }
    
    // 处理预览帧的代理方法
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        // 获取原始帧的CVImageBuffer
        guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
            return
        }
        
        // 锁定缓冲区内存
        CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)
        
        defer {
            // 处理完后解锁缓冲区
            CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly)
        }
        
        // 获取Y分量的地址和字节数(如果是YUV双平面格式)
        let yBaseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0)
        let yWidth = CVPixelBufferGetWidthOfPlane(imageBuffer, 0)
        let yHeight = CVPixelBufferGetHeightOfPlane(imageBuffer, 0)
        let yBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0)
        let ySize = yBytesPerRow * yHeight
        
        // 复制Y分量数据到字节数组
        let yData = Data(bytes: yBaseAddress!, count: ySize)
        let yByteArray = [UInt8](yData)
        
        // 如果需要处理UV分量,类似上面的方式获取第二平面的数据
        let uvBaseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1)
        let uvWidth = CVPixelBufferGetWidthOfPlane(imageBuffer, 1)
        let uvHeight = CVPixelBufferGetHeightOfPlane(imageBuffer, 1)
        let uvBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1)
        let uvSize = uvBytesPerRow * uvHeight
        
        let uvData = Data(bytes: uvBaseAddress!, count: uvSize)
        let uvByteArray = [UInt8](uvData)
        
        // 这里可以把yByteArray和uvByteArray合并成完整的帧字节数组,或者单独处理
        print("获取到Y分量字节数组长度:\(yByteArray.count)")
    }
}

关键细节说明

  • 像素格式选择:示例中用的kCVPixelFormatType_420YpCbCr8BiPlanarFullRange是iOS相机常用的YUV格式,和Android相机预览的NV21格式类似,方便后续处理。
  • 线程安全:代理回调会在我们指定的串行队列中执行,避免阻塞主线程,处理完帧数据后记得及时解锁缓冲区。
  • 前后置相机切换:只需要在获取AVCaptureDevice时把position改成.front即可。

这样你就能像在Android中那样,实时获取相机的原始字节数组帧啦!

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

火山引擎 最新活动