Improve camera frame rate matching on macOS
- Prefer formats with narrower frame rate ranges
- Set frame duration on both device and connection
for better enforcement
diff --git a/src/camera/coremedia/SDL_camera_coremedia.m b/src/camera/coremedia/SDL_camera_coremedia.m
index f02c4ac..308bbbf 100644
--- a/src/camera/coremedia/SDL_camera_coremedia.m
+++ b/src/camera/coremedia/SDL_camera_coremedia.m
@@ -320,10 +320,37 @@
const float FRAMERATE_EPSILON = 0.01f;
for (AVFrameRateRange *framerate in format.videoSupportedFrameRateRanges) {
- if (rate > (framerate.minFrameRate - FRAMERATE_EPSILON) &&
- rate < (framerate.maxFrameRate + FRAMERATE_EPSILON)) {
- spec_format = format;
- break;
+ // Check if the requested rate is within the supported range
+ if (rate >= (framerate.minFrameRate - FRAMERATE_EPSILON) &&
+ rate <= (framerate.maxFrameRate + FRAMERATE_EPSILON)) {
+
+ // Prefer formats with narrower frame rate ranges that are closer to our target
+ // This helps avoid formats that support a wide range (like 10-60 FPS)
+ // when we want a specific rate (like 30 FPS)
+ bool should_select = false;
+ if (spec_format == nil) {
+ should_select = true;
+ } else {
+ AVFrameRateRange *current_range = spec_format.videoSupportedFrameRateRanges.firstObject;
+ float current_range_width = current_range.maxFrameRate - current_range.minFrameRate;
+ float new_range_width = framerate.maxFrameRate - framerate.minFrameRate;
+
+ // Prefer formats with narrower ranges, or if ranges are similar, prefer closer to target
+ if (new_range_width < current_range_width) {
+ should_select = true;
+ } else if (SDL_fabsf(new_range_width - current_range_width) < 0.1f) {
+ // Similar range width, prefer the one closer to our target rate
+ float current_distance = SDL_fabsf(rate - current_range.minFrameRate);
+ float new_distance = SDL_fabsf(rate - framerate.minFrameRate);
+ if (new_distance < current_distance) {
+ should_select = true;
+ }
+ }
+ }
+
+ if (should_select) {
+ spec_format = format;
+ }
}
}
@@ -339,6 +366,22 @@
}
avdevice.activeFormat = spec_format;
+
+ // Try to set the frame duration to enforce the requested frame rate
+ const float frameRate = (float)spec->framerate_numerator / spec->framerate_denominator;
+ const CMTime frameDuration = CMTimeMake(1, (int32_t)frameRate);
+
+ // Check if the device supports setting frame duration
+ if ([avdevice respondsToSelector:@selector(setActiveVideoMinFrameDuration:)] &&
+ [avdevice respondsToSelector:@selector(setActiveVideoMaxFrameDuration:)]) {
+ @try {
+ avdevice.activeVideoMinFrameDuration = frameDuration;
+ avdevice.activeVideoMaxFrameDuration = frameDuration;
+ } @catch (NSException *exception) {
+ // Some devices don't support setting frame duration, that's okay
+ }
+ }
+
[avdevice unlockForConfiguration];
AVCaptureSession *session = [[AVCaptureSession alloc] init];
@@ -394,6 +437,15 @@
}
[session addOutput:output];
+ // Try to set the frame rate on the connection
+ AVCaptureConnection *connection = [output connectionWithMediaType:AVMediaTypeVideo];
+ if (connection && connection.isVideoMinFrameDurationSupported) {
+ connection.videoMinFrameDuration = frameDuration;
+ if (connection.isVideoMaxFrameDurationSupported) {
+ connection.videoMaxFrameDuration = frameDuration;
+ }
+ }
+
[session commitConfiguration];
SDLPrivateCameraData *hidden = [[SDLPrivateCameraData alloc] init];