最近写了一个推流的应用
iphone 的默认配置,最高只支持 4K 60fps
的拍摄极限,很难满足我们的场景需求,所以才出现了这样一个需求,在苹果API 的使用场景下,配置一个不那么完美的 4K+240FPS
的高性能帧率相机;
前言
一张照片的输出,由摄像机的 光圈
、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
,且只有 420f
和 422v
两种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
支持多个摄像头设备同时采集,并输出到AVCaptureVideoDataOutput
或AVCaptureMovieFileOutput
等output
对象 ;当业务侧有类似的需求可以采用此方法同时开启多个摄像头;
具体实现代码可以访问 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摄相头的情况,不开启前置摄像头的情况 ;
注:遇到的坑
在VideoToolBox
为 AVC
的情况下,60FPS
的采集会丢帧;
在VideoToolBox
为 HEVC
的情况下,60FPS
的采集则一切正常;
参考链接:
推荐链接 相机工作原理