AVCaptureSession 实现一个假的4K+240FPS

最近写了一个推流的应用
iphone 的默认配置,最高只支持 4K 60fps 的拍摄极限,很难满足我们的场景需求,所以才出现了这样一个需求,在苹果API 的使用场景下,配置一个不那么完美的 4K+240FPS的高性能帧率相机;

maxresdefault2.jpeg
前言

一张照片的输出,由摄像机的 光圈ISO曝光时间来决定,由于iphone 的光圈是固定的,所以只剩ISO曝光时间 两项参数能调整了, 曝光时间相当于控制了相机的快门, 1/50 相当于每 20ms 捕捉一张照片,快门时间越短,从镜头进入相机感光件的近况量越小,画面持续时间越短,用来拍摄运动的物体能达到不错的效果,但是画质也会变暗,所以还需要调整ISO的值;

如果是拍摄静止的物体则 曝光时间 则影响的是画面的亮度,尤其是在光照充足的情况下,曝光时间越长画面越亮, 当 曝光时间设置不合理的时候,还会出现曝光过度的情况;

ISO是用来控制相机图像传感器感光度的,ISO值越大,感光器越灵敏,但是噪点也越高;

Part1: AVCaptureDevice自定义 曝光时间 / ISO

首先明确一点,这种配置是不推荐的,只有特殊场景下,当系统的自动曝光的配置不满足需求的时候,因为苹果默认最高支持 4K +60 FPS 的组合才需要我们去自定义曝光时间和ISO,产品的需求是 4K + 慢动作 ,就是又要超高清画质 又需要捕捉到运动的画面,主要是为了识别运动员身上的身份号牌

- (void)xxx {

     _cameraExposureDuration = CMTimeMake(1,240);
     _cameraISO = 125;
    AVCaptureDevice *cameraDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    NSError *error;
    if ([cameraDevice lockForConfiguration:&error] && cameraDevice.focusPointOfInterestSupported) {
        /// 曝光 、ISO、
        if ([cameraDevice isExposureModeSupported:AVCaptureExposureModeCustom]) {
            cameraDevice.exposureMode = AVCaptureExposureModeCustom;
            [cameraDevice setExposureModeCustomWithDuration:_cameraExposureDuration ISO:_cameraISO completionHandler:^(CMTime syncTime) {}];
        }
        [cameraDevice unlockForConfiguration];
    }
}

AVCaptureDevice FPS 配置 (普通)

- (void)_updateFps:(NSUInteger)fps {
    AVCaptureDevice *cameraDevice = [self _activCamera];
    float maxRate = [(AVFrameRateRange *)[cameraDevice.activeFormat.videoSupportedFrameRateRanges objectAtIndex:0] maxFrameRate];
     //实际修改fps的代码
     if ([cameraDevice lockForConfiguration:NULL]) {
         cameraDevice.activeVideoMinFrameDuration = CMTimeMake(1, (int)fps);
         cameraDevice.activeVideoMaxFrameDuration = CMTimeMake(1, (int)fps);
         [cameraDevice unlockForConfiguration];
         NSLog(@"set fps success fps:%lu maxRate:%f",fps,maxRate);
     }
}

AVCaptureDevice FPS 配置 (高帧率)

目前了解到的情况是 4K分辨率下,最高支持 60FPS,且只有 420f422v两种FormatType格式支持;
当只考虑高FPS 不考虑 FormatType 的情况下,最高可以支持240FPS,但是采集的分辨率 最高1080P;

kCVPixelFormatType_420YpCbCr8BiPlanarFullRange = '420f'
kCVPixelFormatType_422YpCbCr8BiPlanarVideoRange = '422v'

- (void)_updateFps:(NSUInteger)fps {
    AVCaptureDevice *cameraDevice = [self _activCamera];
    CGSize captureSize = CGSizeMake(MIN(_videoConfig.width, _videoConfig.height), MAX(_videoConfig.width, _videoConfig.height));
    /// hight fps (greater 30)
    for (AVCaptureDeviceFormat *videoFormat in [cameraDevice formats] ) {
        CMFormatDescriptionRef description = videoFormat.formatDescription;
        CMVideoDimensions dims = CMVideoFormatDescriptionGetDimensions(description);
        FourCharCode pixelFormat = CMFormatDescriptionGetMediaSubType(description);
        CGSize formatSize = CGSizeMake(MIN(dims.width, dims.height), MAX(dims.width,dims.height));
        float maxRate = [(AVFrameRateRange *)[videoFormat.videoSupportedFrameRateRanges objectAtIndex:0] maxFrameRate];
        if (maxRate >= fps && CGSizeEqualToSize(captureSize, formatSize) && pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
            if ([cameraDevice lockForConfiguration:NULL] == YES) {
                cameraDevice.activeFormat = videoFormat;
                cameraDevice.activeVideoMinFrameDuration = CMTimeMake(1, (int32_t)fps);
                cameraDevice.activeVideoMaxFrameDuration = CMTimeMake(1, (int32_t)fps);
                [cameraDevice unlockForConfiguration];
                NSLog(@"set fps success fps:%lu maxRate:%f pixelFormat:%d {%f,%f}",fps,maxRate,pixelFormat,formatSize.width,formatSize.height);
            }
        }
    }
    
}

AVCaptureVideoDataOutput 并非严格每秒30FPS

AVCaptureDevice采集时,通过AVCaptureVideoDataOutput获取采集到的sampleBuffer时,因为需要多设备之间 NTP时钟 同步,需要在采集后拿到设备的当前时间;发现摄像头采集的周期,并非严格按照我们的参数 30FPS,输出,反而是时间戳有一些偏移,时间越久累计的偏移量越多;


预计结果:

配置的分辨率是4K、30FPS,在采集不丢帧的情况下,预期2秒打印一次,在精度为毫秒千分位,时间戳应该是在千分位上下稳定;

实际输出结果:

时间戳在向后移动,1分钟的情况下偏移了 14ms

网友提问的连接:
https://developer.apple.com/forums/thread/23307

Part2 :AVCaptureMultiCamSession多摄像头同时采集

AVCaptureMultiCamSession 支持多个摄像头设备同时采集,并输出到AVCaptureVideoDataOutputAVCaptureMovieFileOutputoutput对象 ;当业务侧有类似的需求可以采用此方法同时开启多个摄像头;
具体实现代码可以访问 MutableCapture-Demo

目前发现的问题:

当设备后置摄像头为三个同时开启,则前置摄像头不能打开;
当必须开启前置摄像头的情况,则后置三摄像头,其中一个无法加入到Session 会话中
不知道是不是苹果的限制,但是API 也没有说明这一点
不支持分辨率设置

关于AVCaptureDeviceType的详细描述可以参考如下汇总;

AVCaptureDeviceTypeBuiltInMichrophone  麦克风设备
AVCaptureDeviceTypeBuiltInWideAngleCamera 内置广角相机设备
AVCaptureDeviceTypeBuiltInTelephotoCamera  内置的相机设备 焦距比广角相机长。注意: 此类设备只能使用 AVCaptureDeviceDiscoverySession 发现
AVCaptureDeviceTypeBuiltInUltraWideCamera  比广角相机焦距短的内置相机设备。注意: 此类设备只能使用 AVCaptureDeviceDiscoverySession 发现
AVCaptureDeviceTypeBuiltInDualCamera  一种由两个固定的焦距照相机组成的设备。一个是广角镜头(Wide),一个是远摄镜头 (Telephoto)。
AVCaptureDeviceTypeBuiltInDualWideCamera 一种由两个固定焦距照相机组成的设备。一个是超宽镜头(Ultra Wide),一个是广角镜头(Wide Angle)。
AVCaptureDeviceTypeBuiltInTripleCamera 一种有三个固定焦距照相机组成的设备。一个超宽镜头(Ultra Wide),一个广角镜头(Wide Angle)和一个远摄镜头(Telephoto)组成。
AVCaptureDeviceTypeBuiltInTrueDepthCamera  一种由两台摄像机组成的设备。一台 YUV 和一台红外线。红外线摄像头可提供高质的深度信息,该信息可与 YUV 摄像头产生的帧同步并进行透视纠正。两台摄像头的分辨率可能不通透,但他们的相同的纵横比。

多摄像头的场景下获取AVCaptureDevice不能通过defaultDeviceWithMediaType 这种 API 来获取,只能通过AVCaptureDeviceDiscoverySession相关discoverySessionWithDeviceTypes 等方法来获取想要的设备,这点需要注意;

展示结果如下图所示:

开启前置摄像头的情况

具体实现的部分代码可参考如下:

- (void)setupTelephoneCamera {
    /// 长焦摄像头
    AVCaptureDevice *device = [self _deviceWithPosition:AVCaptureDevicePositionBack devicetype:AVCaptureDeviceTypeBuiltInTelephotoCamera];
    if (!device) return;
        
    //前置摄像头
    self.dualDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
    if ([self.captureSession canAddInput:self.dualDeviceInput]) {
        [self.captureSession addInputWithNoConnections:self.dualDeviceInput];
    }
    
    //视频输出
    self.dualVideoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
    if ([self.captureSession canAddOutput:self.dualVideoDataOutput]) {
        [self.captureSession addOutputWithNoConnections:self.dualVideoDataOutput];
    }

    NSArray *ports =  [self.dualDeviceInput portsWithMediaType:AVMediaTypeVideo sourceDeviceType:device.deviceType sourceDevicePosition:device.position];
    self.dualVideoConnection = [AVCaptureConnection connectionWithInputPorts:ports output:self.dualVideoDataOutput];
    self.dualVideoConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
    if ([self.captureSession canAddConnection:self.dualVideoConnection]) {
        [self.captureSession addConnection:self.dualVideoConnection];
    }
    
    AVCaptureVideoPreviewLayer *layer = self.telephoneLayer;
    [layer setSessionWithNoConnection:self.captureSession];
    layer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    [self.telephoneView.layer addSublayer:layer];
    
    AVCaptureConnection *layerConnection = [AVCaptureConnection connectionWithInputPort:ports.firstObject videoPreviewLayer:self.telephoneLayer];
    if ([self.captureSession canAddConnection:layerConnection]) {
        [self.captureSession addConnection:layerConnection];
    }
}


- (AVCaptureDevice *)_deviceWithPosition:(AVCaptureDevicePosition)position devicetype:(AVCaptureDeviceType)devicetype {
    NSArray *devices;
    if (@available(iOS 11.1, *)) {
        NSArray *deviceTypes = @[devicetype];
        AVCaptureDeviceDiscoverySession *videoDeviceDiscoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:deviceTypes mediaType:AVMediaTypeVideo position:position];
        devices = videoDeviceDiscoverySession.devices;
    } else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
#pragma clang diagnostic pop
    }
    for (AVCaptureDevice *device in devices) {
        if ([device position] == position) {
            return device;
        }
    }
    return nil;
}

下图所示是 后置3摄相头的情况,不开启前置摄像头的情况 ;

后置三摄全部打开的情况

注:遇到的坑

VideoToolBoxAVC的情况下,60FPS 的采集会丢帧;
VideoToolBoxHEVC 的情况下,60FPS 的采集则一切正常;

参考链接:

推荐链接 相机工作原理

你可能感兴趣的:(AVCaptureSession 实现一个假的4K+240FPS)