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
This commit is contained in:
Qiu Qiang
2025-12-08 21:55:17 -05:00
committed by Sam Lantinga
parent b3cdeb942a
commit 62639fdf88

View File

@@ -320,10 +320,37 @@ static bool COREMEDIA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
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 @@ static bool COREMEDIA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
}
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 @@ static bool COREMEDIA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
}
[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];