avfvideosrc: Don't wait on main thread for permissions request

Recursively invoking the NSMainLoop can cause crashes in
applications that don't expect it. Instead of waiting for
permission to be granted, move the wait later - until we
actually need device permissions when starting the capture
session. That moves the wait into the streaming thread
instead of the application thread that's setting the pipeline
state to READY.

Instead of a manual state change implementation to open
and close the device, use the basesrc start/stop methods that
are intended for the purpose.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4083>
This commit is contained in:
Jan Schmidt 2023-03-01 06:32:19 +11:00 committed by GStreamer Marge Bot
parent 95ca40c4ab
commit 855e86f6a2

View file

@ -176,6 +176,10 @@ gst_avf_video_source_device_type_get_type (void)
AVCaptureConnection *connection; AVCaptureConnection *connection;
CMClockRef inputClock; CMClockRef inputClock;
NSCondition *permissionCond;
BOOL permissionRequestPending;
BOOL permissionStopRequest;
dispatch_queue_t mainQueue; dispatch_queue_t mainQueue;
dispatch_queue_t workerQueue; dispatch_queue_t workerQueue;
NSConditionLock *bufQueueLock; NSConditionLock *bufQueueLock;
@ -244,7 +248,6 @@ gst_avf_video_source_device_type_get_type (void)
- (BOOL)unlockStop; - (BOOL)unlockStop;
- (BOOL)query:(GstQuery *)query; - (BOOL)query:(GstQuery *)query;
- (void)setContext:(GstContext *)context; - (void)setContext:(GstContext *)context;
- (GstStateChangeReturn)changeState:(GstStateChange)transition;
- (GstFlowReturn)create:(GstBuffer **)buf; - (GstFlowReturn)create:(GstBuffer **)buf;
- (GstCaps *)fixate:(GstCaps *)caps; - (GstCaps *)fixate:(GstCaps *)caps;
- (BOOL)decideAllocation:(GstQuery *)query; - (BOOL)decideAllocation:(GstQuery *)query;
@ -332,6 +335,8 @@ static AVCaptureVideoOrientation GstAVFVideoSourceOrientation2AVCaptureVideoOrie
workerQueue = workerQueue =
dispatch_queue_create ("org.freedesktop.gstreamer.avfvideosrc.output", NULL); dispatch_queue_create ("org.freedesktop.gstreamer.avfvideosrc.output", NULL);
permissionCond = [[NSCondition alloc] init];
gst_base_src_set_live (baseSrc, TRUE); gst_base_src_set_live (baseSrc, TRUE);
gst_base_src_set_format (baseSrc, GST_FORMAT_TIME); gst_base_src_set_format (baseSrc, GST_FORMAT_TIME);
} }
@ -343,6 +348,8 @@ static AVCaptureVideoOrientation GstAVFVideoSourceOrientation2AVCaptureVideoOrie
{ {
mainQueue = NULL; mainQueue = NULL;
workerQueue = NULL; workerQueue = NULL;
permissionCond = nil;
} }
- (BOOL)openDeviceInput - (BOOL)openDeviceInput
@ -350,6 +357,52 @@ static AVCaptureVideoOrientation GstAVFVideoSourceOrientation2AVCaptureVideoOrie
NSString *mediaType = AVMediaTypeVideo; NSString *mediaType = AVMediaTypeVideo;
NSError *err; NSError *err;
// Since Mojave, permissions are now supposed to be explicitly granted
// before capturing from the camera
if (@available(macOS 10.14, *)) {
// Check if permission has already been granted (or denied)
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
switch (authStatus) {
case AVAuthorizationStatusDenied:
// The user has explicitly denied permission for media capture.
GST_ELEMENT_ERROR (element, RESOURCE, NOT_AUTHORIZED,
("Device video access permission has been explicitly denied before"), ("Authorization status: %d", (int)authStatus));
return NO;
case AVAuthorizationStatusRestricted:
// The user is not allowed to access media capture devices.
GST_ELEMENT_ERROR (element, RESOURCE, NOT_AUTHORIZED,
("Device video access permission cannot be granted by the user"), ("Authorization status: %d", (int)authStatus));
return NO;
case AVAuthorizationStatusAuthorized:
// The user has explicitly granted permission for media capture,
// or explicit user permission is not necessary for the media type in question.
GST_DEBUG_OBJECT (element, "Device video access permission has already been granted");
break;
case AVAuthorizationStatusNotDetermined:
// Explicit user permission is required for media capture,
// but the user has not yet granted or denied such permission.
GST_DEBUG_OBJECT (element, "Requesting device video access permission");
[permissionCond lock];
permissionRequestPending = YES;
[permissionCond unlock];
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
GST_DEBUG_OBJECT (element, "Device video access permission %s", granted ? "granted" : "not granted");
// Check if permission has been granted
if (!granted) {
GST_ELEMENT_ERROR (element, RESOURCE, NOT_AUTHORIZED,
("Device video access permission has been denied"), ("Authorization status: %d", (int)AVAuthorizationStatusDenied));
}
[permissionCond lock];
permissionRequestPending = NO;
[permissionCond broadcast];
[permissionCond unlock];
}];
break;
}
}
if (deviceIndex == DEFAULT_DEVICE_INDEX) { if (deviceIndex == DEFAULT_DEVICE_INDEX) {
#ifdef HAVE_IOS #ifdef HAVE_IOS
if (deviceType != DEFAULT_DEVICE_TYPE && position != DEFAULT_POSITION) { if (deviceType != DEFAULT_DEVICE_TYPE && position != DEFAULT_POSITION) {
@ -453,60 +506,6 @@ static AVCaptureVideoOrientation GstAVFVideoSourceOrientation2AVCaptureVideoOrie
GST_DEBUG_OBJECT (element, "Opening device"); GST_DEBUG_OBJECT (element, "Opening device");
// Since Mojave, permissions are now supposed to be explicitly granted
// before performing anything on a device
if (@available(macOS 10.14, *)) {
if (captureScreen)
goto checked;
// Check if permission has already been granted (or denied)
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
switch (authStatus) {
case AVAuthorizationStatusDenied:
// The user has explicitly denied permission for media capture.
GST_ELEMENT_ERROR (element, RESOURCE, NOT_AUTHORIZED,
("Device video access permission has been explicitly denied before"), ("Authorization status: %d", (int)authStatus));
return success;
case AVAuthorizationStatusRestricted:
// The user is not allowed to access media capture devices.
GST_ELEMENT_ERROR (element, RESOURCE, NOT_AUTHORIZED,
("Device video access permission cannot be granted by the user"), ("Authorization status: %d", (int)authStatus));
return success;
case AVAuthorizationStatusAuthorized:
// The user has explicitly granted permission for media capture,
// or explicit user permission is not necessary for the media type in question.
GST_DEBUG_OBJECT (element, "Device video access permission has already been granted");
break;
case AVAuthorizationStatusNotDetermined:
;
// Explicit user permission is required for media capture,
// but the user has not yet granted or denied such permission.
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_sync (mainQueue, ^{
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
GST_DEBUG_OBJECT (element, "Device video access permission %s", granted ? "granted" : "not granted");
dispatch_semaphore_signal(sema);
}];
});
// Block on dialog being answered
if (![NSThread isMainThread]) {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
} else {
while (dispatch_semaphore_wait(sema, DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0]];
}
}
// Check if permission has been granted
AVAuthorizationStatus videoAuthorizationStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if (videoAuthorizationStatus != AVAuthorizationStatusAuthorized) {
GST_ELEMENT_ERROR (element, RESOURCE, NOT_AUTHORIZED,
("Device video access permission has just been denied"), ("Authorization status: %d", (int)videoAuthorizationStatus));
return success;
}
}
}
checked:
dispatch_sync (mainQueue, ^{ dispatch_sync (mainQueue, ^{
BOOL ret; BOOL ret;
@ -535,11 +534,10 @@ checked:
connection.videoOrientation = GstAVFVideoSourceOrientation2AVCaptureVideoOrientation(orientation); connection.videoOrientation = GstAVFVideoSourceOrientation2AVCaptureVideoOrientation(orientation);
#endif #endif
inputClock = ((AVCaptureInputPort *)connection.inputPorts[0]).clock; inputClock = ((AVCaptureInputPort *)connection.inputPorts[0]).clock;
*successPtr = YES; *successPtr = YES;
}); });
GST_DEBUG_OBJECT (element, "Opening device %s", success ? "succeed" : "failed"); GST_DEBUG_OBJECT (element, "Opening device %s", success ? "succeeded" : "failed");
return success; return success;
} }
@ -915,8 +913,24 @@ checked:
gst_caps_replace (&caps, new_caps); gst_caps_replace (&caps, new_caps);
GST_INFO_OBJECT (element, "configured caps %"GST_PTR_FORMAT, caps); GST_INFO_OBJECT (element, "configured caps %"GST_PTR_FORMAT, caps);
if (![session isRunning]) if (![session isRunning]) {
[session startRunning]; BOOL stopping = NO;
/* If permissions are still pending, wait for a response before
* starting the capture running, or else we'll get black frames */
[permissionCond lock];
if (permissionRequestPending && !permissionStopRequest) {
GST_DEBUG_OBJECT (element, "Waiting for pending device access permission.");
do {
[permissionCond wait];
} while (permissionRequestPending && !permissionStopRequest);
}
stopping = permissionStopRequest;
[permissionCond unlock];
if (!stopping)
[session startRunning];
}
/* Unlock device configuration only after session is started so the session /* Unlock device configuration only after session is started so the session
* won't reset the capture formats */ * won't reset the capture formats */
@ -928,6 +942,14 @@ checked:
- (BOOL)start - (BOOL)start
{ {
[permissionCond lock];
permissionRequestPending = NO;
permissionStopRequest = NO;
[permissionCond unlock];
if (![self openDevice])
return NO;
bufQueueLock = [[NSConditionLock alloc] initWithCondition:NO_BUFFERS]; bufQueueLock = [[NSConditionLock alloc] initWithCondition:NO_BUFFERS];
bufQueue = [[NSMutableArray alloc] initWithCapacity:BUFFER_QUEUE_SIZE]; bufQueue = [[NSMutableArray alloc] initWithCapacity:BUFFER_QUEUE_SIZE];
stopRequest = NO; stopRequest = NO;
@ -958,6 +980,8 @@ checked:
gst_gl_context_helper_free (ctxh); gst_gl_context_helper_free (ctxh);
ctxh = NULL; ctxh = NULL;
[self closeDevice];
return YES; return YES;
} }
@ -990,6 +1014,11 @@ checked:
stopRequest = YES; stopRequest = YES;
[bufQueueLock unlockWithCondition:HAS_BUFFER_OR_STOP_REQUEST]; [bufQueueLock unlockWithCondition:HAS_BUFFER_OR_STOP_REQUEST];
[permissionCond lock];
permissionStopRequest = YES;
[permissionCond broadcast];
[permissionCond unlock];
return YES; return YES;
} }
@ -999,26 +1028,13 @@ checked:
stopRequest = NO; stopRequest = NO;
[bufQueueLock unlockWithCondition:([bufQueue count] == 0) ? NO_BUFFERS : HAS_BUFFER_OR_STOP_REQUEST]; [bufQueueLock unlockWithCondition:([bufQueue count] == 0) ? NO_BUFFERS : HAS_BUFFER_OR_STOP_REQUEST];
[permissionCond lock];
permissionStopRequest = NO;
[permissionCond unlock];
return YES; return YES;
} }
- (GstStateChangeReturn)changeState:(GstStateChange)transition
{
GstStateChangeReturn ret;
if (transition == GST_STATE_CHANGE_NULL_TO_READY) {
if (![self openDevice])
return GST_STATE_CHANGE_FAILURE;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
if (transition == GST_STATE_CHANGE_READY_TO_NULL)
[self closeDevice];
return ret;
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput - (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)aConnection fromConnection:(AVCaptureConnection *)aConnection
@ -1290,8 +1306,6 @@ static void gst_avf_video_src_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec); GValue * value, GParamSpec * pspec);
static void gst_avf_video_src_set_property (GObject * object, guint prop_id, static void gst_avf_video_src_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec); const GValue * value, GParamSpec * pspec);
static GstStateChangeReturn gst_avf_video_src_change_state (
GstElement * element, GstStateChange transition);
static GstCaps * gst_avf_video_src_get_caps (GstBaseSrc * basesrc, static GstCaps * gst_avf_video_src_get_caps (GstBaseSrc * basesrc,
GstCaps * filter); GstCaps * filter);
static gboolean gst_avf_video_src_set_caps (GstBaseSrc * basesrc, static gboolean gst_avf_video_src_set_caps (GstBaseSrc * basesrc,
@ -1323,7 +1337,6 @@ gst_avf_video_src_class_init (GstAVFVideoSrcClass * klass)
gobject_class->get_property = gst_avf_video_src_get_property; gobject_class->get_property = gst_avf_video_src_get_property;
gobject_class->set_property = gst_avf_video_src_set_property; gobject_class->set_property = gst_avf_video_src_set_property;
gstelement_class->change_state = gst_avf_video_src_change_state;
gstelement_class->set_context = gst_avf_video_src_set_context; gstelement_class->set_context = gst_avf_video_src_set_context;
gstbasesrc_class->get_caps = gst_avf_video_src_get_caps; gstbasesrc_class->get_caps = gst_avf_video_src_get_caps;
@ -1540,16 +1553,6 @@ gst_avf_video_src_set_property (GObject * object, guint prop_id,
} }
} }
static GstStateChangeReturn
gst_avf_video_src_change_state (GstElement * element, GstStateChange transition)
{
GstStateChangeReturn ret;
ret = [GST_AVF_VIDEO_SRC_IMPL (element) changeState: transition];
return ret;
}
static GstCaps * static GstCaps *
gst_avf_video_src_get_caps (GstBaseSrc * basesrc, GstCaps * filter) gst_avf_video_src_get_caps (GstBaseSrc * basesrc, GstCaps * filter)
{ {