iOS: Add ifdef'd support for video file input

This commit is contained in:
Miriam Zimmerman 2026-01-30 12:11:03 -05:00 committed by GitHub
parent 862b85cd3f
commit 3fa7eb2a5a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 109 additions and 34 deletions

View File

@ -56,6 +56,7 @@ Pod::Spec.new do |s|
'CARGO_BUILD_TARGET[sdk=iphonesimulator*][arch=arm64]' => 'aarch64-apple-ios-sim',
'CARGO_BUILD_TARGET[sdk=iphonesimulator*][arch=*]' => 'x86_64-apple-ios',
'CARGO_BUILD_TARGET[sdk=iphoneos*]' => 'aarch64-apple-ios',
'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => ENV.include?('RINGRTC_USE_FILE_BASED_CAMERA') ? 'USE_FILE_BASED_CAMERA' : '',
}
s.script_phases = [

View File

@ -12,18 +12,41 @@ public class VideoCaptureController {
static let maxCaptureHeight: Int32 = 720
static let maxCaptureFrameRate: Int32 = 30
// Keep around for captureSession even if USE_FILE_BASED_CAMERA
private let capturer = RTCCameraVideoCapturer()
var capturerDelegate: RTCVideoCapturerDelegate? {
set { capturer.delegate = newValue }
get { capturer.delegate }
}
private let serialQueue = DispatchQueue(label: "org.signal.videoCaptureController")
#if USE_FILE_BASED_CAMERA
private var fileCapturer = RTCFileVideoCapturer()
private var delegate: RTCVideoCapturerDelegate?
var capturerDelegate: RTCVideoCapturerDelegate? {
set {
self.delegate = newValue
let wasCapturing = self.isCapturing
if wasCapturing {
self.stopCapture()
}
self.fileCapturer = RTCFileVideoCapturer.init(
delegate: newValue!
)
if wasCapturing {
self.startCapture()
}
}
get { self.delegate }
}
#else
var capturerDelegate: RTCVideoCapturerDelegate? {
set { capturer.delegate = newValue }
get { capturer.delegate }
}
#endif
private let serialQueue = DispatchQueue(
label: "org.signal.videoCaptureController"
)
private var _isUsingFrontCamera: Bool = true
public var isUsingFrontCamera: Bool? {
get {
serialQueue.sync { [weak self] in
return self?._isUsingFrontCamera
}
serialQueue.sync { [weak self] in
return self?._isUsingFrontCamera
}
}
private var isCapturing: Bool = false
@ -62,7 +85,11 @@ public class VideoCaptureController {
// a crash on iOS 13 when built with the iOS 13 SDK.
guard strongSelf.isCapturing else { return }
strongSelf.capturer.stopCapture()
#if USE_FILE_BASED_CAMERA
strongSelf.fileCapturer.stopCapture()
#else
strongSelf.capturer.stopCapture()
#endif
strongSelf.isCapturing = false
}
}
@ -78,7 +105,9 @@ public class VideoCaptureController {
// Only restart capturing again if the camera changes.
if strongSelf._isUsingFrontCamera != isUsingFrontCamera {
strongSelf._isUsingFrontCamera = isUsingFrontCamera
strongSelf.startCaptureSync()
#if !USE_FILE_BASED_CAMERA
strongSelf.startCaptureSync()
#endif
}
}
}
@ -93,24 +122,44 @@ public class VideoCaptureController {
Logger.info("startCaptureSync():")
assertIsOnSerialQueue()
let position: AVCaptureDevice.Position = _isUsingFrontCamera ? .front : .back
guard let device: AVCaptureDevice = self.device(position: position) else {
failDebug("unable to find captureDevice")
return
}
#if USE_FILE_BASED_CAMERA
fileCapturer.startCapturing(
fromFileNamed: "input_video.mp4",
onError: { (error: Error) -> Void in
Logger.error("Failed to start capturing: \(error)")
}
)
#else
guard let format: AVCaptureDevice.Format = self.format(device: device) else {
failDebug("unable to find captureDevice")
return
}
let position: AVCaptureDevice.Position =
_isUsingFrontCamera ? .front : .back
guard let device: AVCaptureDevice = self.device(position: position)
else {
failDebug("unable to find captureDevice")
return
}
guard
let format: AVCaptureDevice.Format = self.format(device: device)
else {
failDebug("unable to find captureDevice")
return
}
capturer.startCapture(
with: device,
format: format,
fps: Int(VideoCaptureController.maxCaptureFrameRate)
)
#endif
capturer.startCapture(with: device, format: format, fps: Int(VideoCaptureController.maxCaptureFrameRate))
isCapturing = true
}
private func device(position: AVCaptureDevice.Position) -> AVCaptureDevice? {
private func device(position: AVCaptureDevice.Position) -> AVCaptureDevice?
{
let captureDevices = RTCCameraVideoCapturer.captureDevices()
guard let device = (captureDevices.first { $0.position == position }) else {
guard let device = (captureDevices.first { $0.position == position })
else {
Logger.debug("unable to find desired position: \(position)")
return captureDevices.first
}
@ -124,7 +173,7 @@ public class VideoCaptureController {
CChar(pixelFormat >> 16 & 0xFF),
CChar(pixelFormat >> 8 & 0xFF),
CChar(pixelFormat & 0xFF),
0
0,
]
var subTypeString = ""
@ -145,37 +194,62 @@ public class VideoCaptureController {
// on all devices the client supports.
let screenSize = UIScreen.main.nativeBounds.size
// screenSize is given in portrait-up orientation, but capture dimensions are in landscape.
let targetWidth = max(Int32(screenSize.height), VideoCaptureController.maxCaptureWidth)
let targetHeight = max(Int32(screenSize.width), VideoCaptureController.maxCaptureHeight)
let targetWidth = max(
Int32(screenSize.height),
VideoCaptureController.maxCaptureWidth
)
let targetHeight = max(
Int32(screenSize.width),
VideoCaptureController.maxCaptureHeight
)
let targetFrameRate = VideoCaptureController.maxCaptureFrameRate
Logger.info("Capture Formats")
Logger.info(" screenSize: \(screenSize)")
Logger.info(" maxCaptureWidth: \(VideoCaptureController.maxCaptureWidth)")
Logger.info(" maxCaptureHeight: \(VideoCaptureController.maxCaptureHeight)")
Logger.info(
" maxCaptureWidth: \(VideoCaptureController.maxCaptureWidth)"
)
Logger.info(
" maxCaptureHeight: \(VideoCaptureController.maxCaptureHeight)"
)
Logger.info(" targetWidth: \(targetWidth)")
Logger.info(" targetHeight: \(targetHeight)")
Logger.info(" targetFrameRate: \(targetFrameRate)")
Logger.info(" preferredPixelFormat: \(getSubTypeString(pixelFormat: capturer.preferredOutputPixelFormat()))")
#if !USE_FILE_BASED_CAMERA
// Not there on RTCFileVideoCapture
Logger.info(
" preferredPixelFormat: \(getSubTypeString(pixelFormat: capturer.preferredOutputPixelFormat()))"
)
#endif
Logger.debug(" formats:")
var selectedFormat: AVCaptureDevice.Format?
var currentDiff: Int32 = Int32.max
for format in formats {
let dimension = CMVideoFormatDescriptionGetDimensions(format.formatDescription)
let pixelFormat = CMFormatDescriptionGetMediaSubType(format.formatDescription);
let dimension = CMVideoFormatDescriptionGetDimensions(
format.formatDescription
)
let pixelFormat = CMFormatDescriptionGetMediaSubType(
format.formatDescription
)
for range in format.videoSupportedFrameRateRanges {
Logger.debug(" width: \(dimension.width) height: \(dimension.height) pixelFormat: \(getSubTypeString(pixelFormat: pixelFormat)) fps range: \(range.minFrameRate) - \(range.maxFrameRate)")
Logger.debug(
" width: \(dimension.width) height: \(dimension.height) pixelFormat: \(getSubTypeString(pixelFormat: pixelFormat)) fps range: \(range.minFrameRate) - \(range.maxFrameRate)"
)
}
let diff = abs(targetWidth - dimension.width) + abs(targetHeight - dimension.height)
let diff =
abs(targetWidth - dimension.width)
+ abs(targetHeight - dimension.height)
if diff < currentDiff {
// Look through all framerate ranges for this capture format and find
// the first that supports the desired framerate.
for range in format.videoSupportedFrameRateRanges {
if Double(targetFrameRate) >= range.minFrameRate && Double(targetFrameRate) <= range.maxFrameRate {
if Double(targetFrameRate) >= range.minFrameRate
&& Double(targetFrameRate) <= range.maxFrameRate
{
selectedFormat = format
currentDiff = diff
}