avfvideosrc: Guess reasonable framerate values for some 3rd party devices

For some third-party devices macOS sometimes reports silly framerates,
like 750003/6250 instead of 120/1. To avoid users having to exactly
pecify those values, we instead report the closest reasonable value in
caps. If it ends up being chosen, the additional logic in
setDeviceCaps() will reverse that process and pass the actual supported
value back to AVF, as most often the rounding causes us to fall just
outside the accepted threshold.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/8745>
This commit is contained in:
Piotr Brzeziński 2025-03-31 18:54:28 +02:00 committed by GStreamer Marge Bot
parent 4e65d16bfc
commit 50d2d1446f

View File

@ -53,6 +53,9 @@ GST_DEBUG_CATEGORY (gst_avf_video_src_debug);
static CMVideoDimensions
get_oriented_dimensions(GstAVFVideoSourceOrientation orientation, CMVideoDimensions dimensions);
static CMTime
find_range_bound_close_enough_to_fps (AVFrameRateRange *range, int fps_n, int fps_d);
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
@ -662,8 +665,11 @@ G_GNUC_END_IGNORE_DEPRECATIONS
device.activeFormat = fmt;
for (AVFrameRateRange *range in fmt.videoSupportedFrameRateRanges) {
CMTime dur = CMTimeMake (info->fps_d, info->fps_n);
if (CMTIME_COMPARE_INLINE (range.minFrameDuration, <=, dur) &&
CMTIME_COMPARE_INLINE (range.maxFrameDuration, >=, dur)) {
// Either the value is directly supported, or we rounded it to the nearest 'sane' value in get_caps(),
// in which case AVF might refuse to accept it, so we try to use one of the range bounds if it's close enough.
if ((CMTIME_COMPARE_INLINE (range.minFrameDuration, <=, dur) &&
CMTIME_COMPARE_INLINE (range.maxFrameDuration, >=, dur)) ||
CMTIME_IS_VALID (dur = find_range_bound_close_enough_to_fps (range, info->fps_n, info->fps_d))) {
device.activeVideoMinFrameDuration = dur;
device.activeVideoMaxFrameDuration = dur;
found_framerate = TRUE;
@ -1581,16 +1587,20 @@ gst_av_capture_device_get_caps (AVCaptureDevice *device, AVCaptureVideoDataOutpu
for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) {
int min_fps_n, min_fps_d, max_fps_n, max_fps_d;
GstClockTime min_dur, max_dur;
/* CMTime duration is the inverse of fps*/
min_fps_n = range.maxFrameDuration.timescale;
min_fps_d = range.maxFrameDuration.value;
max_fps_n = range.minFrameDuration.timescale;
max_fps_d = range.minFrameDuration.value;
// For some third-party devices macOS sometimes reports silly framerates, like 750003/6250 instead of 120/1.
// Let's round all values here just in case, and then reverse that in setDeviceCaps when passing values to AVF.
// Note: In the past, min/maxFrameRate were used instead, but that also came with its own set of rounding issues.
min_dur = gst_util_uint64_scale (GST_SECOND, range.minFrameDuration.value, range.minFrameDuration.timescale);
max_dur = gst_util_uint64_scale (GST_SECOND, range.maxFrameDuration.value, range.maxFrameDuration.timescale);
GST_DEBUG ("dimensions %ix%i fps range is [%i/%i, %i/%i]",
dimensions.width, dimensions.height, min_fps_n, min_fps_d, max_fps_n,
max_fps_d);
gst_video_guess_framerate (min_dur, &max_fps_n, &max_fps_d);
gst_video_guess_framerate (max_dur, &min_fps_n, &min_fps_d);
GST_DEBUG ("dimensions %ix%i, duration range %s, assuming fps range [%d/%d - %d/%d]",
dimensions.width, dimensions.height, [range description].UTF8String,
min_fps_n, min_fps_d, max_fps_n, max_fps_d);
for (NSNumber *pixel_format in output.availableVideoCVPixelFormatTypes) {
GstCaps *caps;
@ -1659,3 +1669,26 @@ get_oriented_dimensions (GstAVFVideoSourceOrientation orientation, CMVideoDimens
}
return orientedDimensions;
}
static CMTime
find_range_bound_close_enough_to_fps (AVFrameRateRange *range, int fps_n, int fps_d)
{
GstClockTime min_dur, max_dur;
gint guessed_n, guessed_d;
min_dur = gst_util_uint64_scale (GST_SECOND, range.minFrameDuration.value, range.minFrameDuration.timescale);
gst_video_guess_framerate (min_dur, &guessed_n, &guessed_d);
if (guessed_n == fps_n && guessed_d == fps_d) {
GST_DEBUG ("Using min duration from range %s for fps %d/%d", [range description].UTF8String, fps_n, fps_d);
return range.minFrameDuration;
}
max_dur = gst_util_uint64_scale (GST_SECOND, range.maxFrameDuration.value, range.maxFrameDuration.timescale);
gst_video_guess_framerate (max_dur, &guessed_n, &guessed_d);
if (guessed_n == fps_n && guessed_d == fps_d) {
GST_DEBUG ("Using max duration from range %s for fps %d/%d", [range description].UTF8String, fps_n, fps_d);
return range.maxFrameDuration;
}
return kCMTimeInvalid;
}