feat: Support 10-bit Video HDR (#1827)
* feat: Select 10-bit YUV HDR format if HDR is enabled * fix: Remove video EDR setting in favor of new 10-bit video HDR * Format Swift
This commit is contained in:
parent
cf4882b152
commit
9809075507
@ -115,6 +115,7 @@ enum FormatError {
|
|||||||
case invalidFps(fps: Int)
|
case invalidFps(fps: Int)
|
||||||
case invalidHdr
|
case invalidHdr
|
||||||
case invalidFormat
|
case invalidFormat
|
||||||
|
case incompatiblePixelFormatWithHDR
|
||||||
|
|
||||||
var code: String {
|
var code: String {
|
||||||
switch self {
|
switch self {
|
||||||
@ -124,6 +125,8 @@ enum FormatError {
|
|||||||
return "invalid-fps"
|
return "invalid-fps"
|
||||||
case .invalidHdr:
|
case .invalidHdr:
|
||||||
return "invalid-hdr"
|
return "invalid-hdr"
|
||||||
|
case .incompatiblePixelFormatWithHDR:
|
||||||
|
return "incompatible-pixel-format-with-hdr-setting"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +137,9 @@ enum FormatError {
|
|||||||
case let .invalidFps(fps):
|
case let .invalidFps(fps):
|
||||||
return "The given format cannot run at \(fps) FPS! Make sure your FPS is lower than `format.maxFps` but higher than `format.minFps`."
|
return "The given format cannot run at \(fps) FPS! Make sure your FPS is lower than `format.maxFps` but higher than `format.minFps`."
|
||||||
case .invalidHdr:
|
case .invalidHdr:
|
||||||
return "The currently selected format does not support HDR capture! Make sure you select a format which includes `supportsPhotoHDR`!"
|
return "The currently selected format does not support HDR capture! Make sure you select a format which includes `supportsPhotoHDR`/`supportsVideoHDR`!"
|
||||||
|
case .incompatiblePixelFormatWithHDR:
|
||||||
|
return "The currently selected pixelFormat is not compatible with HDR! HDR only works with the `yuv` pixelFormat."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,34 +117,10 @@ extension CameraView {
|
|||||||
videoOutput!.setSampleBufferDelegate(self, queue: CameraQueues.videoQueue)
|
videoOutput!.setSampleBufferDelegate(self, queue: CameraQueues.videoQueue)
|
||||||
videoOutput!.alwaysDiscardsLateVideoFrames = false
|
videoOutput!.alwaysDiscardsLateVideoFrames = false
|
||||||
|
|
||||||
if let pixelFormat = pixelFormat as? String {
|
let pixelFormatType = getPixelFormat(videoOutput: videoOutput!)
|
||||||
let supportedPixelFormats = videoOutput!.availableVideoPixelFormatTypes
|
videoOutput!.videoSettings = [
|
||||||
let defaultFormat = supportedPixelFormats.first! // first value is always the most efficient format
|
String(kCVPixelBufferPixelFormatTypeKey): pixelFormatType,
|
||||||
var pixelFormatType: OSType = defaultFormat
|
]
|
||||||
switch pixelFormat {
|
|
||||||
case "yuv":
|
|
||||||
if supportedPixelFormats.contains(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
|
|
||||||
pixelFormatType = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
|
|
||||||
} else if supportedPixelFormats.contains(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) {
|
|
||||||
pixelFormatType = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
|
|
||||||
} else {
|
|
||||||
invokeOnError(.device(.pixelFormatNotSupported))
|
|
||||||
}
|
|
||||||
case "rgb":
|
|
||||||
if supportedPixelFormats.contains(kCVPixelFormatType_32BGRA) {
|
|
||||||
pixelFormatType = kCVPixelFormatType_32BGRA
|
|
||||||
} else {
|
|
||||||
invokeOnError(.device(.pixelFormatNotSupported))
|
|
||||||
}
|
|
||||||
case "native":
|
|
||||||
pixelFormatType = defaultFormat
|
|
||||||
default:
|
|
||||||
invokeOnError(.parameter(.invalid(unionName: "pixelFormat", receivedValue: pixelFormat)))
|
|
||||||
}
|
|
||||||
videoOutput!.videoSettings = [
|
|
||||||
String(kCVPixelBufferPixelFormatTypeKey): pixelFormatType,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
captureSession.addOutput(videoOutput!)
|
captureSession.addOutput(videoOutput!)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,6 +133,64 @@ extension CameraView {
|
|||||||
ReactLogger.log(level: .info, message: "Session successfully configured!")
|
ReactLogger.log(level: .info, message: "Session successfully configured!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns the pixel format that should be used for the AVCaptureVideoDataOutput.
|
||||||
|
If HDR is enabled, this will return YUV 4:2:0 10-bit.
|
||||||
|
If HDR is disabled, this will return whatever the user specified as a pixelFormat, or the most efficient format as a fallback.
|
||||||
|
*/
|
||||||
|
private func getPixelFormat(videoOutput: AVCaptureVideoDataOutput) -> OSType {
|
||||||
|
let supportedPixelFormats = videoOutput.availableVideoPixelFormatTypes
|
||||||
|
// as per documentation, the first value is always the most efficient format
|
||||||
|
let defaultFormat = supportedPixelFormats.first!
|
||||||
|
|
||||||
|
// If the user enabled HDR, we can only use the YUV 4:2:0 10-bit pixel format.
|
||||||
|
if hdr == true {
|
||||||
|
guard pixelFormat == nil || pixelFormat == "yuv" else {
|
||||||
|
invokeOnError(.format(.incompatiblePixelFormatWithHDR))
|
||||||
|
return defaultFormat
|
||||||
|
}
|
||||||
|
guard supportedPixelFormats.contains(kCVPixelFormatType_420YpCbCr10BiPlanarFullRange) else {
|
||||||
|
invokeOnError(.format(.invalidHdr))
|
||||||
|
return defaultFormat
|
||||||
|
}
|
||||||
|
// YUV 4:2:0 10-bit
|
||||||
|
return kCVPixelFormatType_420YpCbCr10BiPlanarFullRange
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user didn't specify a custom pixelFormat, just return the default one.
|
||||||
|
guard let pixelFormat = pixelFormat else {
|
||||||
|
return defaultFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't use HDR, we can use any other custom pixel format.
|
||||||
|
switch pixelFormat {
|
||||||
|
case "yuv":
|
||||||
|
if supportedPixelFormats.contains(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
|
||||||
|
// YUV 4:2:0 8-bit (full video colors)
|
||||||
|
return kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
|
||||||
|
} else if supportedPixelFormats.contains(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) {
|
||||||
|
// YUV 4:2:0 8-bit (limited video colors)
|
||||||
|
return kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
|
||||||
|
} else {
|
||||||
|
invokeOnError(.device(.pixelFormatNotSupported))
|
||||||
|
return defaultFormat
|
||||||
|
}
|
||||||
|
case "rgb":
|
||||||
|
if supportedPixelFormats.contains(kCVPixelFormatType_32BGRA) {
|
||||||
|
// RGBA 8-bit
|
||||||
|
return kCVPixelFormatType_32BGRA
|
||||||
|
} else {
|
||||||
|
invokeOnError(.device(.pixelFormatNotSupported))
|
||||||
|
return defaultFormat
|
||||||
|
}
|
||||||
|
case "native":
|
||||||
|
return defaultFormat
|
||||||
|
default:
|
||||||
|
invokeOnError(.parameter(.invalid(unionName: "pixelFormat", receivedValue: pixelFormat as String)))
|
||||||
|
return defaultFormat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// pragma MARK: Configure Device
|
// pragma MARK: Configure Device
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -188,17 +222,6 @@ extension CameraView {
|
|||||||
device.activeVideoMinFrameDuration = CMTime.invalid
|
device.activeVideoMinFrameDuration = CMTime.invalid
|
||||||
device.activeVideoMaxFrameDuration = CMTime.invalid
|
device.activeVideoMaxFrameDuration = CMTime.invalid
|
||||||
}
|
}
|
||||||
if hdr != nil {
|
|
||||||
if hdr == true && !device.activeFormat.isVideoHDRSupported {
|
|
||||||
invokeOnError(.format(.invalidHdr))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !device.automaticallyAdjustsVideoHDREnabled {
|
|
||||||
if device.isVideoHDREnabled != hdr!.boolValue {
|
|
||||||
device.isVideoHDREnabled = hdr!.boolValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if lowLightBoost != nil {
|
if lowLightBoost != nil {
|
||||||
if lowLightBoost == true && !device.isLowLightBoostSupported {
|
if lowLightBoost == true && !device.isLowLightBoostSupported {
|
||||||
invokeOnError(.device(.lowLightBoostNotSupported))
|
invokeOnError(.device(.lowLightBoostNotSupported))
|
||||||
|
@ -26,9 +26,9 @@ private let propsThatRequireReconfiguration = ["cameraId",
|
|||||||
"photo",
|
"photo",
|
||||||
"video",
|
"video",
|
||||||
"enableFrameProcessor",
|
"enableFrameProcessor",
|
||||||
|
"hdr",
|
||||||
"pixelFormat"]
|
"pixelFormat"]
|
||||||
private let propsThatRequireDeviceReconfiguration = ["fps",
|
private let propsThatRequireDeviceReconfiguration = ["fps",
|
||||||
"hdr",
|
|
||||||
"lowLightBoost"]
|
"lowLightBoost"]
|
||||||
|
|
||||||
// MARK: - CameraView
|
// MARK: - CameraView
|
||||||
|
@ -50,7 +50,7 @@ extension AVCaptureDevice.Format {
|
|||||||
"minISO": minISO,
|
"minISO": minISO,
|
||||||
"fieldOfView": videoFieldOfView,
|
"fieldOfView": videoFieldOfView,
|
||||||
"maxZoom": videoMaxZoomFactor,
|
"maxZoom": videoMaxZoomFactor,
|
||||||
"supportsVideoHDR": isVideoHDRSupported,
|
"supportsVideoHDR": availablePixelFormats.contains(kCVPixelFormatType_420YpCbCr10BiPlanarFullRange),
|
||||||
"supportsPhotoHDR": false,
|
"supportsPhotoHDR": false,
|
||||||
"minFps": minFrameRate,
|
"minFps": minFrameRate,
|
||||||
"maxFps": maxFrameRate,
|
"maxFps": maxFrameRate,
|
||||||
|
@ -18,6 +18,7 @@ export type DeviceError =
|
|||||||
export type FormatError =
|
export type FormatError =
|
||||||
| 'format/invalid-fps'
|
| 'format/invalid-fps'
|
||||||
| 'format/invalid-hdr'
|
| 'format/invalid-hdr'
|
||||||
|
| 'format/incompatible-pixel-format-with-hdr-setting'
|
||||||
| 'format/invalid-low-light-boost'
|
| 'format/invalid-low-light-boost'
|
||||||
| 'format/invalid-format'
|
| 'format/invalid-format'
|
||||||
| 'format/invalid-color-space';
|
| 'format/invalid-color-space';
|
||||||
|
Loading…
Reference in New Issue
Block a user