avfvideosrc: Fix description and trailing whitespace
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / sys / applemedia / avfvideosrc.m
1 /*
2  * Copyright (C) 2010 Ole André Vadla Ravnås <oleavr@soundrop.com>
3  * Copyright (C) 2016 Alessandro Decina <twi@centricular.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #endif
24
25 #include "avfvideosrc.h"
26 #include "glcontexthelper.h"
27
28 #import <AVFoundation/AVFoundation.h>
29 #if !HAVE_IOS
30 #import <AppKit/AppKit.h>
31 #endif
32 #include <gst/video/video.h>
33 #include <gst/gl/gstglcontext.h>
34 #include "coremediabuffer.h"
35 #include "videotexturecache-gl.h"
36
37 #define DEFAULT_DEVICE_INDEX  -1
38 #define DEFAULT_POSITION      GST_AVF_VIDEO_SOURCE_POSITION_DEFAULT
39 #define DEFAULT_ORIENTATION   GST_AVF_VIDEO_SOURCE_ORIENTATION_DEFAULT
40 #define DEFAULT_DEVICE_TYPE   GST_AVF_VIDEO_SOURCE_DEVICE_TYPE_DEFAULT
41 #define DEFAULT_DO_STATS      FALSE
42
43 #define DEVICE_FPS_N          25
44 #define DEVICE_FPS_D          1
45
46 #define BUFFER_QUEUE_SIZE     2
47
48 GST_DEBUG_CATEGORY (gst_avf_video_src_debug);
49 #define GST_CAT_DEFAULT gst_avf_video_src_debug
50
51 static GstVideoFormat get_gst_video_format(NSNumber *pixel_format);
52 static CMVideoDimensions
53 get_oriented_dimensions(GstAVFVideoSourceOrientation orientation, CMVideoDimensions dimensions);
54
55 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
56     GST_PAD_SRC,
57     GST_PAD_ALWAYS,
58     GST_STATIC_CAPS (
59 #if !HAVE_IOS
60         GST_VIDEO_CAPS_MAKE_WITH_FEATURES
61         (GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
62             "UYVY") ", "
63         "texture-target = " GST_GL_TEXTURE_TARGET_RECTANGLE_STR ";"
64 #else
65         GST_VIDEO_CAPS_MAKE_WITH_FEATURES
66         (GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
67             "NV12") ", "
68         "texture-target = " GST_GL_TEXTURE_TARGET_2D_STR "; "
69 #endif
70         "video/x-raw, "
71         "format = (string) { NV12, UYVY, YUY2 }, "
72         "framerate = " GST_VIDEO_FPS_RANGE ", "
73         "width = " GST_VIDEO_SIZE_RANGE ", "
74         "height = " GST_VIDEO_SIZE_RANGE "; "
75
76         "video/x-raw, "
77         "format = (string) BGRA, "
78         "framerate = " GST_VIDEO_FPS_RANGE ", "
79         "width = " GST_VIDEO_SIZE_RANGE ", "
80         "height = " GST_VIDEO_SIZE_RANGE "; "
81 ));
82
83 typedef enum _QueueState {
84   NO_BUFFERS = 1,
85   HAS_BUFFER_OR_STOP_REQUEST,
86 } QueueState;
87
88 #define gst_avf_video_src_parent_class parent_class
89 G_DEFINE_TYPE (GstAVFVideoSrc, gst_avf_video_src, GST_TYPE_PUSH_SRC);
90
91 #define GST_TYPE_AVF_VIDEO_SOURCE_POSITION (gst_avf_video_source_position_get_type ())
92 static GType
93 gst_avf_video_source_position_get_type (void)
94 {
95   static GType avf_video_source_position_type = 0;
96
97   if (!avf_video_source_position_type) {
98     static GEnumValue position_types[] = {
99       { GST_AVF_VIDEO_SOURCE_POSITION_FRONT, "Front-facing camera", "front" },
100       { GST_AVF_VIDEO_SOURCE_POSITION_BACK,  "Back-facing camera", "back"  },
101       { GST_AVF_VIDEO_SOURCE_POSITION_DEFAULT,  "Default", "default"  },
102       { 0, NULL, NULL },
103     };
104
105     avf_video_source_position_type =
106     g_enum_register_static ("GstAVFVideoSourcePosition",
107                             position_types);
108   }
109
110   return avf_video_source_position_type;
111 }
112
113 #define GST_TYPE_AVF_VIDEO_SOURCE_ORIENTATION (gst_avf_video_source_orientation_get_type ())
114 static GType
115 gst_avf_video_source_orientation_get_type (void)
116 {
117   static GType avf_video_source_orientation_type = 0;
118
119   if (!avf_video_source_orientation_type) {
120     static GEnumValue orientation_types[] = {
121       { GST_AVF_VIDEO_SOURCE_ORIENTATION_PORTRAIT, "Indicates that video should be oriented vertically, top at the top.", "portrait" },
122       { GST_AVF_VIDEO_SOURCE_ORIENTATION_PORTRAIT_UPSIDE_DOWN, "Indicates that video should be oriented vertically, top at the bottom.", "portrat-upside-down" },
123       { GST_AVF_VIDEO_SOURCE_ORIENTATION_LANDSCAPE_RIGHT, "Indicates that video should be oriented horizontally, top on the left.", "landscape-right" },
124       { GST_AVF_VIDEO_SOURCE_ORIENTATION_LANDSCAPE_LEFT, "Indicates that video should be oriented horizontally, top on the right.", "landscape-left" },
125       { GST_AVF_VIDEO_SOURCE_ORIENTATION_DEFAULT, "Default", "default" },
126       { 0, NULL, NULL },
127     };
128
129     avf_video_source_orientation_type =
130     g_enum_register_static ("GstAVFVideoSourceOrientation",
131                             orientation_types);
132   }
133
134   return avf_video_source_orientation_type;
135 }
136
137 #define GST_TYPE_AVF_VIDEO_SOURCE_DEVICE_TYPE (gst_avf_video_source_device_type_get_type ())
138 static GType
139 gst_avf_video_source_device_type_get_type (void)
140 {
141   static GType avf_video_source_device_type_type = 0;
142
143   if (!avf_video_source_device_type_type) {
144     static GEnumValue device_type_types[] = {
145       { GST_AVF_VIDEO_SOURCE_DEVICE_TYPE_BUILT_IN_WIDE_ANGLE_CAMERA, "A built-in wide angle camera. These devices are suitable for general purpose use.", "wide-angle" },
146       { GST_AVF_VIDEO_SOURCE_DEVICE_TYPE_BUILT_IN_TELEPHOTO_CAMERA, "A built-in camera device with a longer focal length than a wide-angle camera.", "telephoto" },
147       { GST_AVF_VIDEO_SOURCE_DEVICE_TYPE_BUILT_IN_DUAL_CAMERA, "A dual camera device, combining built-in wide-angle and telephoto cameras that work together as a single capture device.", "dual" },
148       { GST_AVF_VIDEO_SOURCE_DEVICE_TYPE_DEFAULT, "Default", "default" },
149       { 0, NULL, NULL },
150     };
151
152     avf_video_source_device_type_type =
153     g_enum_register_static ("GstAVFVideoSourceDeviceType",
154                             device_type_types);
155   }
156
157   return avf_video_source_device_type_type;
158 }
159
160 @interface GstAVFVideoSrcImpl : NSObject <AVCaptureVideoDataOutputSampleBufferDelegate> {
161   GstElement *element;
162   GstBaseSrc *baseSrc;
163   GstPushSrc *pushSrc;
164
165   gint deviceIndex;
166   const gchar *deviceName;
167   GstAVFVideoSourcePosition position;
168   GstAVFVideoSourceOrientation orientation;
169   GstAVFVideoSourceDeviceType deviceType;
170   BOOL doStats;
171
172   AVCaptureSession *session;
173   AVCaptureInput *input;
174   AVCaptureVideoDataOutput *output;
175   AVCaptureDevice *device;
176   AVCaptureConnection *connection;
177   CMClockRef inputClock;
178
179   NSCondition *permissionCond;
180   BOOL permissionRequestPending;
181   BOOL permissionStopRequest;
182
183   dispatch_queue_t mainQueue;
184   dispatch_queue_t workerQueue;
185   NSConditionLock *bufQueueLock;
186   NSMutableArray *bufQueue;
187   BOOL stopRequest;
188
189   GstCaps *caps;
190   GstVideoFormat format;
191   gint width, height;
192   GstClockTime latency;
193   guint64 offset;
194
195   GstClockTime lastSampling;
196   guint count;
197   gint fps;
198   BOOL captureScreen;
199   BOOL captureScreenCursor;
200   BOOL captureScreenMouseClicks;
201   guint cropX;
202   guint cropY;
203   guint cropWidth;
204   guint cropHeight;
205
206   BOOL useVideoMeta;
207   GstGLContextHelper *ctxh;
208   GstVideoTextureCache *textureCache;
209 }
210
211 - (id)init;
212 - (id)initWithSrc:(GstPushSrc *)src;
213 - (void)finalize;
214
215 @property int deviceIndex;
216 @property const gchar *deviceName;
217 @property GstAVFVideoSourcePosition position;
218 @property GstAVFVideoSourceOrientation orientation;
219 @property GstAVFVideoSourceDeviceType deviceType;
220 @property BOOL doStats;
221 @property int fps;
222 @property BOOL captureScreen;
223 @property BOOL captureScreenCursor;
224 @property BOOL captureScreenMouseClicks;
225 @property guint cropX;
226 @property guint cropY;
227 @property guint cropWidth;
228 @property guint cropHeight;
229
230 - (BOOL)openScreenInput;
231 - (BOOL)openDeviceInput;
232 - (BOOL)openDevice;
233 - (void)closeDevice;
234 - (GstVideoFormat)getGstVideoFormat:(NSNumber *)pixel_format;
235 #if !HAVE_IOS
236 - (CGDirectDisplayID)getDisplayIdFromDeviceIndex;
237 - (float)getScaleFactorFromDeviceIndex;
238 #endif
239 - (GstCaps *)getDeviceCaps;
240 - (BOOL)setDeviceCaps:(GstVideoInfo *)info;
241 - (BOOL)getSessionPresetCaps:(GstCaps *)result;
242 - (BOOL)setSessionPresetCaps:(GstVideoInfo *)info;
243 - (GstCaps *)getCaps;
244 - (BOOL)setCaps:(GstCaps *)new_caps;
245 - (BOOL)start;
246 - (BOOL)stop;
247 - (BOOL)unlock;
248 - (BOOL)unlockStop;
249 - (BOOL)query:(GstQuery *)query;
250 - (void)setContext:(GstContext *)context;
251 - (GstFlowReturn)create:(GstBuffer **)buf;
252 - (GstCaps *)fixate:(GstCaps *)caps;
253 - (BOOL)decideAllocation:(GstQuery *)query;
254 - (void)updateStatistics;
255 - (void)captureOutput:(AVCaptureOutput *)captureOutput
256 didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
257        fromConnection:(AVCaptureConnection *)connection;
258
259 @end
260
261 #if HAVE_IOS
262
263 static AVCaptureDeviceType GstAVFVideoSourceDeviceType2AVCaptureDeviceType(GstAVFVideoSourceDeviceType deviceType) {
264   switch (deviceType) {
265     case GST_AVF_VIDEO_SOURCE_DEVICE_TYPE_BUILT_IN_WIDE_ANGLE_CAMERA:
266       return AVCaptureDeviceTypeBuiltInWideAngleCamera;
267     case GST_AVF_VIDEO_SOURCE_DEVICE_TYPE_BUILT_IN_TELEPHOTO_CAMERA:
268       return AVCaptureDeviceTypeBuiltInTelephotoCamera;
269     case GST_AVF_VIDEO_SOURCE_DEVICE_TYPE_BUILT_IN_DUAL_CAMERA:
270       return AVCaptureDeviceTypeBuiltInDuoCamera;
271     case GST_AVF_VIDEO_SOURCE_DEVICE_TYPE_DEFAULT:
272       g_assert_not_reached();
273   }
274 }
275
276 static AVCaptureDevicePosition GstAVFVideoSourcePosition2AVCaptureDevicePosition(GstAVFVideoSourcePosition position) {
277   switch (position) {
278     case GST_AVF_VIDEO_SOURCE_POSITION_FRONT:
279       return AVCaptureDevicePositionFront;
280     case GST_AVF_VIDEO_SOURCE_POSITION_BACK:
281       return AVCaptureDevicePositionBack;
282     case GST_AVF_VIDEO_SOURCE_POSITION_DEFAULT:
283       g_assert_not_reached();
284   }
285
286 }
287
288 static AVCaptureVideoOrientation GstAVFVideoSourceOrientation2AVCaptureVideoOrientation(GstAVFVideoSourceOrientation orientation) {
289   switch (orientation) {
290     case GST_AVF_VIDEO_SOURCE_ORIENTATION_PORTRAIT:
291       return AVCaptureVideoOrientationPortrait;
292     case GST_AVF_VIDEO_SOURCE_ORIENTATION_PORTRAIT_UPSIDE_DOWN:
293       return AVCaptureVideoOrientationPortraitUpsideDown;
294     case GST_AVF_VIDEO_SOURCE_ORIENTATION_LANDSCAPE_LEFT:
295       return AVCaptureVideoOrientationLandscapeLeft;
296     case GST_AVF_VIDEO_SOURCE_ORIENTATION_LANDSCAPE_RIGHT:
297       return AVCaptureVideoOrientationLandscapeRight;
298     case GST_AVF_VIDEO_SOURCE_ORIENTATION_DEFAULT:
299       g_assert_not_reached();
300   }
301 }
302
303 #endif
304
305 @implementation GstAVFVideoSrcImpl
306
307 @synthesize deviceIndex, deviceName, position, orientation, deviceType, doStats,
308     fps, captureScreen, captureScreenCursor, captureScreenMouseClicks, cropX, cropY, cropWidth, cropHeight;
309
310 - (id)init
311 {
312   return [self initWithSrc:NULL];
313 }
314
315 - (id)initWithSrc:(GstPushSrc *)src
316 {
317   if ((self = [super init])) {
318     element = GST_ELEMENT_CAST (src);
319     baseSrc = GST_BASE_SRC_CAST (src);
320     pushSrc = src;
321
322     deviceIndex = DEFAULT_DEVICE_INDEX;
323     deviceName = NULL;
324     position = DEFAULT_POSITION;
325     orientation = DEFAULT_ORIENTATION;
326     deviceType = DEFAULT_DEVICE_TYPE;
327     captureScreen = NO;
328     captureScreenCursor = NO;
329     captureScreenMouseClicks = NO;
330     useVideoMeta = NO;
331     textureCache = NULL;
332     ctxh = gst_gl_context_helper_new (element);
333     mainQueue =
334         dispatch_queue_create ("org.freedesktop.gstreamer.avfvideosrc.main", NULL);
335     workerQueue =
336         dispatch_queue_create ("org.freedesktop.gstreamer.avfvideosrc.output", NULL);
337
338     permissionCond = [[NSCondition alloc] init];
339
340     gst_base_src_set_live (baseSrc, TRUE);
341     gst_base_src_set_format (baseSrc, GST_FORMAT_TIME);
342   }
343
344   return self;
345 }
346
347 - (void)finalize
348 {
349   mainQueue = NULL;
350   workerQueue = NULL;
351
352   permissionCond = nil;
353 }
354
355 - (BOOL)openDeviceInput
356 {
357   NSString *mediaType = AVMediaTypeVideo;
358   NSError *err;
359
360   // Since Mojave, permissions are now supposed to be explicitly granted
361   // before capturing from the camera
362   if (@available(macOS 10.14, *)) {
363     // Check if permission has already been granted (or denied)
364     AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
365     switch (authStatus) {
366       case AVAuthorizationStatusDenied:
367         // The user has explicitly denied permission for media capture.
368         GST_ELEMENT_ERROR (element, RESOURCE, NOT_AUTHORIZED,
369           ("Device video access permission has been explicitly denied before"), ("Authorization status: %d", (int)authStatus));
370           return NO;
371       case AVAuthorizationStatusRestricted:
372         // The user is not allowed to access media capture devices.
373         GST_ELEMENT_ERROR (element, RESOURCE, NOT_AUTHORIZED,
374           ("Device video access permission cannot be granted by the user"), ("Authorization status: %d", (int)authStatus));
375         return NO;
376       case AVAuthorizationStatusAuthorized:
377         // The user has explicitly granted permission for media capture,
378         // or explicit user permission is not necessary for the media type in question.
379         GST_DEBUG_OBJECT (element, "Device video access permission has already been granted");
380         break;
381       case AVAuthorizationStatusNotDetermined:
382         // Explicit user permission is required for media capture,
383         // but the user has not yet granted or denied such permission.
384         GST_DEBUG_OBJECT (element, "Requesting device video access permission");
385
386         [permissionCond lock];
387         permissionRequestPending = YES;
388         [permissionCond unlock];
389
390         [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
391           GST_DEBUG_OBJECT (element, "Device video access permission %s", granted ? "granted" : "not granted");
392           // Check if permission has been granted
393           if (!granted) {
394              GST_ELEMENT_ERROR (element, RESOURCE, NOT_AUTHORIZED,
395                ("Device video access permission has been denied"), ("Authorization status: %d", (int)AVAuthorizationStatusDenied));
396           }
397           [permissionCond lock];
398           permissionRequestPending = NO;
399           [permissionCond broadcast];
400           [permissionCond unlock];
401         }];
402         break;
403     }
404   }
405
406   if (deviceIndex == DEFAULT_DEVICE_INDEX) {
407 #ifdef HAVE_IOS
408     if (deviceType != DEFAULT_DEVICE_TYPE && position != DEFAULT_POSITION) {
409       device = [AVCaptureDevice
410                 defaultDeviceWithDeviceType:GstAVFVideoSourceDeviceType2AVCaptureDeviceType(deviceType)
411                 mediaType:mediaType
412                 position:GstAVFVideoSourcePosition2AVCaptureDevicePosition(position)];
413     } else {
414       device = [AVCaptureDevice defaultDeviceWithMediaType:mediaType];
415     }
416 #else
417       device = [AVCaptureDevice defaultDeviceWithMediaType:mediaType];
418 #endif
419     if (device == nil) {
420       GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
421                           ("No video capture devices found"), (NULL));
422       return NO;
423     }
424   } else { // deviceIndex takes priority over position and deviceType
425     NSArray *devices = [AVCaptureDevice devicesWithMediaType:mediaType];
426     if (deviceIndex >= [devices count]) {
427       GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
428                           ("Invalid video capture device index"), (NULL));
429       return NO;
430     }
431     device = [devices objectAtIndex:deviceIndex];
432   }
433   g_assert (device != nil);
434
435   deviceName = [[device localizedName] UTF8String];
436   GST_INFO ("Opening '%s'", deviceName);
437
438   input = [AVCaptureDeviceInput deviceInputWithDevice:device
439                                                 error:&err];
440   if (input == nil) {
441     GST_ELEMENT_ERROR (element, RESOURCE, BUSY,
442         ("Failed to open device: %s",
443         [[err localizedDescription] UTF8String]),
444         (NULL));
445     device = nil;
446     return NO;
447   }
448   return YES;
449 }
450
451 - (BOOL)openScreenInput
452 {
453 #if HAVE_IOS
454   return NO;
455 #else
456   CGDirectDisplayID displayId;
457   int screenHeight, screenWidth;
458
459   GST_DEBUG_OBJECT (element, "Opening screen input");
460
461   displayId = [self getDisplayIdFromDeviceIndex];
462   if (displayId == 0)
463     return NO;
464
465   AVCaptureScreenInput *screenInput =
466       [[AVCaptureScreenInput alloc] initWithDisplayID:displayId];
467
468   @try {
469     [screenInput setValue:[NSNumber numberWithBool:captureScreenCursor]
470                  forKey:@"capturesCursor"];
471
472   } @catch (NSException *exception) {
473     if (![[exception name] isEqualToString:NSUndefinedKeyException]) {
474       GST_WARNING ("An unexpected error occurred: %s",
475                    [[exception reason] UTF8String]);
476     }
477     GST_WARNING ("Capturing cursor is only supported in OS X >= 10.8");
478   }
479
480   screenHeight = CGDisplayPixelsHigh (displayId);
481   screenWidth = CGDisplayPixelsWide (displayId);
482
483   if (cropX + cropWidth > screenWidth || cropY + cropHeight > screenHeight) {
484     GST_WARNING ("Capture region outside of screen bounds, ignoring");
485   } else {
486     /* If width/height is not specified, assume max possible values */
487     int rectWidth = cropWidth ? cropWidth : (screenWidth - cropX);
488     int rectHeight = cropHeight ? cropHeight : (screenHeight - cropY);
489
490     /* cropRect (0,0) is bottom left, which feels counterintuitive.
491      * Make cropY relative to the top edge instead */
492     CGRect cropRect = CGRectMake (cropX, screenHeight - cropY - rectHeight,
493                                   rectWidth, rectHeight);
494     [screenInput setCropRect:cropRect];
495   }
496
497   screenInput.capturesMouseClicks = captureScreenMouseClicks;
498   input = screenInput;
499   return YES;
500 #endif
501 }
502
503 - (BOOL)openDevice
504 {
505   BOOL success = NO, *successPtr = &success;
506
507   GST_DEBUG_OBJECT (element, "Opening device");
508
509   dispatch_sync (mainQueue, ^{
510     BOOL ret;
511
512     if (captureScreen)
513       ret = [self openScreenInput];
514     else
515       ret = [self openDeviceInput];
516
517     if (!ret)
518       return;
519
520     output = [[AVCaptureVideoDataOutput alloc] init];
521     [output setSampleBufferDelegate:self
522                               queue:workerQueue];
523     output.alwaysDiscardsLateVideoFrames = YES;
524     output.videoSettings = nil; /* device native format */
525
526     session = [[AVCaptureSession alloc] init];
527     [session addInput:input];
528     [session addOutput:output];
529
530     /* retained by session */
531     connection = [[output connections] firstObject];
532 #ifdef HAVE_IOS
533     if (orientation != DEFAULT_ORIENTATION)
534       connection.videoOrientation = GstAVFVideoSourceOrientation2AVCaptureVideoOrientation(orientation);
535 #endif
536     inputClock = ((AVCaptureInputPort *)connection.inputPorts[0]).clock;
537     *successPtr = YES;
538   });
539
540   GST_DEBUG_OBJECT (element, "Opening device %s", success ? "succeeded" : "failed");
541
542   return success;
543 }
544
545 - (void)closeDevice
546 {
547   GST_DEBUG_OBJECT (element, "Closing device");
548
549   dispatch_sync (mainQueue, ^{
550     g_assert (![session isRunning]);
551
552     connection = nil;
553     inputClock = nil;
554
555     [session removeInput:input];
556     [session removeOutput:output];
557
558     session = nil;
559
560     input = nil;
561
562     output = nil;
563
564     if (!captureScreen) {
565       device = nil;
566     }
567
568     if (caps)
569       gst_caps_unref (caps);
570     caps = NULL;
571   });
572 }
573
574 #define GST_AVF_CAPS_NEW(format, w, h, fps_n, fps_d)                  \
575     (gst_caps_new_simple ("video/x-raw",                              \
576         "width", G_TYPE_INT, w,                                       \
577         "height", G_TYPE_INT, h,                                      \
578         "format", G_TYPE_STRING, gst_video_format_to_string (format), \
579         "framerate", GST_TYPE_FRACTION, (fps_n), (fps_d),             \
580         NULL))
581
582 #define GST_AVF_FPS_RANGE_CAPS_NEW(format, w, h, min_fps_n, min_fps_d, max_fps_n, max_fps_d) \
583     (gst_caps_new_simple ("video/x-raw",                              \
584         "width", G_TYPE_INT, w,                                       \
585         "height", G_TYPE_INT, h,                                      \
586         "format", G_TYPE_STRING, gst_video_format_to_string (format), \
587         "framerate", GST_TYPE_FRACTION_RANGE, (min_fps_n), (min_fps_d), (max_fps_n), (max_fps_d), \
588         NULL))
589
590 - (GstVideoFormat)getGstVideoFormat:(NSNumber *)pixel_format
591 {
592   GstVideoFormat gst_format = get_gst_video_format(pixel_format);
593   if (gst_format == GST_VIDEO_FORMAT_UNKNOWN) {
594     GST_LOG_OBJECT (element, "Pixel format %s is not handled by avfvideosrc",
595         [[pixel_format stringValue] UTF8String]);
596   }
597   return gst_format;
598 }
599
600 #if !HAVE_IOS
601 - (CGDirectDisplayID)getDisplayIdFromDeviceIndex
602 {
603   NSDictionary *description;
604   NSNumber *displayId;
605   NSArray *screens = [NSScreen screens];
606
607   if (deviceIndex == DEFAULT_DEVICE_INDEX)
608     return kCGDirectMainDisplay;
609   if (deviceIndex >= [screens count]) {
610     GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
611                         ("Invalid screen capture device index"), (NULL));
612     return 0;
613   }
614   description = [[screens objectAtIndex:deviceIndex] deviceDescription];
615   displayId = [description objectForKey:@"NSScreenNumber"];
616   return [displayId unsignedIntegerValue];
617 }
618
619 - (float)getScaleFactorFromDeviceIndex
620 {
621   NSArray *screens = [NSScreen screens];
622
623   if (deviceIndex == DEFAULT_DEVICE_INDEX)
624     return [[NSScreen mainScreen] backingScaleFactor];
625   if (deviceIndex >= [screens count]) {
626     GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
627                         ("Invalid screen capture device index"), (NULL));
628     return 1.0;
629   }
630   return [[screens objectAtIndex:deviceIndex] backingScaleFactor];
631 }
632 #endif
633
634
635 - (CMVideoDimensions)orientedDimensions:(CMVideoDimensions)dimensions
636 {
637   return get_oriented_dimensions(orientation, dimensions);
638 }
639
640 - (GstCaps *)getDeviceCaps
641 {
642   GST_DEBUG_OBJECT (element, "Getting device caps");
643   GstCaps *device_caps = gst_av_capture_device_get_caps (device, output, orientation);
644   GST_DEBUG_OBJECT (element, "Device returned the following caps %" GST_PTR_FORMAT, device_caps);
645
646   return device_caps;
647 }
648
649 - (BOOL)setDeviceCaps:(GstVideoInfo *)info
650 {
651   double framerate;
652   gboolean found_format = FALSE, found_framerate = FALSE;
653   NSArray *formats = [device valueForKey:@"formats"];
654   gst_util_fraction_to_double (info->fps_n, info->fps_d, &framerate);
655
656   GST_DEBUG_OBJECT (element, "Setting device caps");
657
658   if ([device lockForConfiguration:NULL] == YES) {
659     for (NSObject *f in formats) {
660       CMFormatDescriptionRef formatDescription = (__bridge CMFormatDescriptionRef) [f performSelector:@selector(formatDescription)];
661       CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(formatDescription);
662       dimensions = [self orientedDimensions:dimensions];
663       if (dimensions.width == info->width && dimensions.height == info->height) {
664         found_format = TRUE;
665         [device setValue:f forKey:@"activeFormat"];
666         for (NSObject *rate in [f valueForKey:@"videoSupportedFrameRateRanges"]) {
667           gdouble min_frame_rate, max_frame_rate;
668
669           [[rate valueForKey:@"minFrameRate"] getValue:&min_frame_rate];
670           [[rate valueForKey:@"maxFrameRate"] getValue:&max_frame_rate];
671           if ((framerate >= min_frame_rate - 0.00001) &&
672               (framerate <= max_frame_rate + 0.00001)) {
673             NSValue *frame_duration_value;
674             found_framerate = TRUE;
675             if (min_frame_rate == max_frame_rate) {
676               /* on mac we get tight ranges and an exception is raised if the
677                * frame duration doesn't match the one reported in the range to
678                * the last decimal point
679                */
680               frame_duration_value = [rate valueForKey:@"minFrameDuration"];
681             } else {
682               // Invert fps_n and fps_d to get frame duration value and timescale (or numerator and denominator)
683               frame_duration_value = [NSValue valueWithCMTime:CMTimeMake (info->fps_d, info->fps_n)];
684             }
685             [device setValue:frame_duration_value forKey:@"activeVideoMinFrameDuration"];
686             @try {
687               /* Only available on OSX >= 10.8 and iOS >= 7.0 */
688               [device setValue:frame_duration_value forKey:@"activeVideoMaxFrameDuration"];
689             } @catch (NSException *exception) {
690               if (![[exception name] isEqualToString:NSUndefinedKeyException]) {
691                 GST_WARNING ("An unexcepted error occurred: %s",
692                               [exception.reason UTF8String]);
693               }
694             }
695             break;
696           }
697         }
698
699         if (found_framerate) {
700           break;
701         }
702       }
703     }
704     if (!found_format) {
705       GST_WARNING ("Unsupported capture dimensions %dx%d", info->width, info->height);
706       return NO;
707     }
708     if (!found_framerate) {
709       GST_WARNING ("Unsupported capture framerate %d/%d", info->fps_n, info->fps_d);
710       return NO;
711     }
712   } else {
713     GST_WARNING ("Couldn't lock device for configuration");
714     return NO;
715   }
716   return YES;
717 }
718
719 - (BOOL)getSessionPresetCaps:(GstCaps *)result
720 {
721   NSArray *pixel_formats = output.availableVideoCVPixelFormatTypes;
722   for (NSNumber *pixel_format in pixel_formats) {
723     GstVideoFormat gst_format = [self getGstVideoFormat:pixel_format];
724     if (gst_format == GST_VIDEO_FORMAT_UNKNOWN)
725       continue;
726
727 #if HAVE_IOS
728     if ([session canSetSessionPreset:AVCaptureSessionPreset1920x1080])
729       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 1920, 1080, DEVICE_FPS_N, DEVICE_FPS_D));
730 #endif
731     if ([session canSetSessionPreset:AVCaptureSessionPreset1280x720])
732       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 1280, 720, DEVICE_FPS_N, DEVICE_FPS_D));
733     if ([session canSetSessionPreset:AVCaptureSessionPreset640x480])
734       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 640, 480, DEVICE_FPS_N, DEVICE_FPS_D));
735     if ([session canSetSessionPreset:AVCaptureSessionPresetMedium])
736       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 480, 360, DEVICE_FPS_N, DEVICE_FPS_D));
737     if ([session canSetSessionPreset:AVCaptureSessionPreset352x288])
738       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 352, 288, DEVICE_FPS_N, DEVICE_FPS_D));
739     if ([session canSetSessionPreset:AVCaptureSessionPresetLow])
740       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 192, 144, DEVICE_FPS_N, DEVICE_FPS_D));
741   }
742
743   GST_LOG_OBJECT (element, "Session presets returned the following caps %" GST_PTR_FORMAT, result);
744
745   return YES;
746 }
747
748 - (BOOL)setSessionPresetCaps:(GstVideoInfo *)info;
749 {
750   GST_DEBUG_OBJECT (element, "Setting session presset caps");
751
752   if ([device lockForConfiguration:NULL] != YES) {
753     GST_WARNING ("Couldn't lock device for configuration");
754     return NO;
755   }
756
757   switch (info->width) {
758   case 192:
759     session.sessionPreset = AVCaptureSessionPresetLow;
760     break;
761   case 352:
762     session.sessionPreset = AVCaptureSessionPreset352x288;
763     break;
764   case 480:
765     session.sessionPreset = AVCaptureSessionPresetMedium;
766     break;
767   case 640:
768     session.sessionPreset = AVCaptureSessionPreset640x480;
769     break;
770   case 1280:
771     session.sessionPreset = AVCaptureSessionPreset1280x720;
772     break;
773 #if HAVE_IOS
774   case 1920:
775     session.sessionPreset = AVCaptureSessionPreset1920x1080;
776     break;
777 #endif
778   default:
779     GST_WARNING ("Unsupported capture dimensions %dx%d", info->width, info->height);
780     return NO;
781   }
782   return YES;
783 }
784
785 - (GstCaps *)getCaps
786 {
787   GstCaps *result;
788   NSArray *pixel_formats;
789
790   if (session == nil)
791     return NULL; /* BaseSrc will return template caps */
792
793   result = gst_caps_new_empty ();
794   pixel_formats = output.availableVideoCVPixelFormatTypes;
795
796   if (captureScreen) {
797 #if !HAVE_IOS
798     CGRect rect;
799     AVCaptureScreenInput *screenInput = (AVCaptureScreenInput *)input;
800     if (CGRectIsEmpty (screenInput.cropRect)) {
801       rect = CGDisplayBounds ([self getDisplayIdFromDeviceIndex]);
802     } else {
803       rect = screenInput.cropRect;
804     }
805
806     float scale = [self getScaleFactorFromDeviceIndex];
807     for (NSNumber *pixel_format in pixel_formats) {
808       GstVideoFormat gst_format = [self getGstVideoFormat:pixel_format];
809       if (gst_format != GST_VIDEO_FORMAT_UNKNOWN)
810         gst_caps_append (result, gst_caps_new_simple ("video/x-raw",
811             "width", G_TYPE_INT, (int)(rect.size.width * scale),
812             "height", G_TYPE_INT, (int)(rect.size.height * scale),
813             "format", G_TYPE_STRING, gst_video_format_to_string (gst_format),
814             NULL));
815     }
816 #else
817     GST_WARNING ("Screen capture is not supported by iOS");
818 #endif
819     return result;
820   }
821
822   @try {
823     result = gst_caps_merge (result, [self getDeviceCaps]);
824   } @catch (NSException *exception) {
825     if (![[exception name] isEqualToString:NSUndefinedKeyException]) {
826       GST_WARNING ("An unexcepted error occurred: %s", [exception.reason UTF8String]);
827       return result;
828     }
829
830     /* Fallback on session presets API for iOS < 7.0 */
831     [self getSessionPresetCaps:result];
832   }
833
834   return result;
835 }
836
837 - (BOOL)setCaps:(GstCaps *)new_caps
838 {
839   GstVideoInfo info;
840   BOOL success = YES, *successPtr = &success;
841
842   gst_video_info_init (&info);
843   gst_video_info_from_caps (&info, new_caps);
844
845   width = info.width;
846   height = info.height;
847   format = info.finfo->format;
848   latency = gst_util_uint64_scale (GST_SECOND, info.fps_d, info.fps_n);
849
850   dispatch_sync (mainQueue, ^{
851     int newformat;
852
853     if (captureScreen) {
854 #if !HAVE_IOS
855       AVCaptureScreenInput *screenInput = (AVCaptureScreenInput *)input;
856       screenInput.minFrameDuration = CMTimeMake(info.fps_d, info.fps_n);
857 #else
858       GST_WARNING ("Screen capture is not supported by iOS");
859       *successPtr = NO;
860       return;
861 #endif
862     } else {
863       @try {
864
865         /* formats and activeFormat keys are only available on OSX >= 10.7 and iOS >= 7.0 */
866         *successPtr = [self setDeviceCaps:(GstVideoInfo *)&info];
867         if (*successPtr != YES)
868           return;
869
870       } @catch (NSException *exception) {
871
872         if (![[exception name] isEqualToString:NSUndefinedKeyException]) {
873           GST_WARNING ("An unexcepted error occurred: %s", [exception.reason UTF8String]);
874           *successPtr = NO;
875           return;
876         }
877
878         /* Fallback on session presets API for iOS < 7.0 */
879         *successPtr = [self setSessionPresetCaps:(GstVideoInfo *)&info];
880         if (*successPtr != YES)
881           return;
882       }
883     }
884
885     switch (format) {
886       case GST_VIDEO_FORMAT_NV12:
887         newformat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
888         break;
889       case GST_VIDEO_FORMAT_UYVY:
890         newformat = kCVPixelFormatType_422YpCbCr8;
891         break;
892       case GST_VIDEO_FORMAT_YUY2:
893         newformat = kCVPixelFormatType_422YpCbCr8_yuvs;
894         break;
895       case GST_VIDEO_FORMAT_BGRA:
896         newformat = kCVPixelFormatType_32BGRA;
897         break;
898       default:
899         *successPtr = NO;
900         GST_WARNING ("Unsupported output format %s",
901             gst_video_format_to_string (format));
902         return;
903     }
904
905     GST_INFO_OBJECT (element,
906         "width: %d height: %d format: %s", width, height,
907         gst_video_format_to_string (format));
908
909     output.videoSettings = [NSDictionary
910         dictionaryWithObject:[NSNumber numberWithInt:newformat]
911         forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey];
912
913     gst_caps_replace (&caps, new_caps);
914     GST_INFO_OBJECT (element, "configured caps %"GST_PTR_FORMAT, caps);
915
916     if (![session isRunning]) {
917       BOOL stopping = NO;
918
919       /* If permissions are still pending, wait for a response before
920        * starting the capture running, or else we'll get black frames */
921       [permissionCond lock];
922       if (permissionRequestPending && !permissionStopRequest) {
923         GST_DEBUG_OBJECT (element, "Waiting for pending device access permission.");
924         do {
925           [permissionCond wait];
926         } while (permissionRequestPending && !permissionStopRequest);
927       }
928       stopping = permissionStopRequest;
929       [permissionCond unlock];
930
931       if (!stopping)
932         [session startRunning];
933     }
934
935     /* Unlock device configuration only after session is started so the session
936      * won't reset the capture formats */
937     [device unlockForConfiguration];
938   });
939
940   return success;
941 }
942
943 - (BOOL)start
944 {
945   [permissionCond lock];
946   permissionRequestPending = NO;
947   permissionStopRequest = NO;
948   [permissionCond unlock];
949
950   if (![self openDevice])
951     return NO;
952
953   bufQueueLock = [[NSConditionLock alloc] initWithCondition:NO_BUFFERS];
954   bufQueue = [[NSMutableArray alloc] initWithCapacity:BUFFER_QUEUE_SIZE];
955   stopRequest = NO;
956
957   offset = 0;
958   latency = GST_CLOCK_TIME_NONE;
959
960   lastSampling = GST_CLOCK_TIME_NONE;
961   count = 0;
962   fps = -1;
963
964   return YES;
965 }
966
967 - (BOOL)stop
968 {
969   dispatch_sync (mainQueue, ^{ [session stopRunning]; });
970   dispatch_sync (workerQueue, ^{});
971
972   bufQueueLock = nil;
973   bufQueue = nil;
974
975   if (textureCache)
976     g_object_unref (textureCache);
977   textureCache = NULL;
978
979   if (ctxh)
980     gst_gl_context_helper_free (ctxh);
981   ctxh = NULL;
982
983   [self closeDevice];
984
985   return YES;
986 }
987
988 - (BOOL)query:(GstQuery *)query
989 {
990   BOOL result = NO;
991
992   if (GST_QUERY_TYPE (query) == GST_QUERY_LATENCY) {
993     if (input != nil && caps != NULL) {
994       GstClockTime min_latency, max_latency;
995
996       min_latency = max_latency = latency;
997       result = YES;
998
999       GST_DEBUG_OBJECT (element, "reporting latency of min %" GST_TIME_FORMAT
1000           " max %" GST_TIME_FORMAT,
1001           GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency));
1002       gst_query_set_latency (query, TRUE, min_latency, max_latency);
1003     }
1004   } else {
1005     result = GST_BASE_SRC_CLASS (parent_class)->query (baseSrc, query);
1006   }
1007
1008   return result;
1009 }
1010
1011 - (BOOL)unlock
1012 {
1013   [bufQueueLock lock];
1014   stopRequest = YES;
1015   [bufQueueLock unlockWithCondition:HAS_BUFFER_OR_STOP_REQUEST];
1016
1017   [permissionCond lock];
1018   permissionStopRequest = YES;
1019   [permissionCond broadcast];
1020   [permissionCond unlock];
1021
1022   return YES;
1023 }
1024
1025 - (BOOL)unlockStop
1026 {
1027   [bufQueueLock lock];
1028   stopRequest = NO;
1029   [bufQueueLock unlockWithCondition:([bufQueue count] == 0) ? NO_BUFFERS : HAS_BUFFER_OR_STOP_REQUEST];
1030
1031   [permissionCond lock];
1032   permissionStopRequest = NO;
1033   [permissionCond unlock];
1034
1035   return YES;
1036 }
1037
1038 - (void)captureOutput:(AVCaptureOutput *)captureOutput
1039 didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
1040        fromConnection:(AVCaptureConnection *)aConnection
1041 {
1042   GstClockTime timestamp, duration;
1043
1044   [bufQueueLock lock];
1045
1046   if (stopRequest) {
1047     [bufQueueLock unlock];
1048     return;
1049   }
1050
1051   [self getSampleBuffer:sampleBuffer timestamp:&timestamp duration:&duration];
1052
1053   if (timestamp == GST_CLOCK_TIME_NONE) {
1054     [bufQueueLock unlockWithCondition:([bufQueue count] == 0) ? NO_BUFFERS : HAS_BUFFER_OR_STOP_REQUEST];
1055     return;
1056   }
1057
1058   if ([bufQueue count] == BUFFER_QUEUE_SIZE)
1059     [bufQueue removeLastObject];
1060
1061   [bufQueue insertObject:@{@"sbuf": (__bridge id)sampleBuffer,
1062                            @"timestamp": @(timestamp),
1063                            @"duration": @(duration)}
1064                  atIndex:0];
1065
1066   [bufQueueLock unlockWithCondition:HAS_BUFFER_OR_STOP_REQUEST];
1067 }
1068
1069 - (GstFlowReturn)create:(GstBuffer **)buf
1070 {
1071   CMSampleBufferRef sbuf;
1072   CVImageBufferRef image_buf;
1073   CVPixelBufferRef pixel_buf;
1074   size_t cur_width, cur_height;
1075   GstClockTime timestamp, duration;
1076
1077   [bufQueueLock lockWhenCondition:HAS_BUFFER_OR_STOP_REQUEST];
1078   if (stopRequest) {
1079     [bufQueueLock unlock];
1080     return GST_FLOW_FLUSHING;
1081   }
1082
1083   NSDictionary *dic = (NSDictionary *) [bufQueue lastObject];
1084   sbuf = (__bridge CMSampleBufferRef) dic[@"sbuf"];
1085   timestamp = (GstClockTime) [dic[@"timestamp"] longLongValue];
1086   duration = (GstClockTime) [dic[@"duration"] longLongValue];
1087   CFRetain (sbuf);
1088   [bufQueue removeLastObject];
1089   [bufQueueLock unlockWithCondition:
1090       ([bufQueue count] == 0) ? NO_BUFFERS : HAS_BUFFER_OR_STOP_REQUEST];
1091
1092   /* Check output frame size dimensions */
1093   image_buf = CMSampleBufferGetImageBuffer (sbuf);
1094   if (image_buf) {
1095     pixel_buf = (CVPixelBufferRef) image_buf;
1096     cur_width = CVPixelBufferGetWidth (pixel_buf);
1097     cur_height = CVPixelBufferGetHeight (pixel_buf);
1098
1099     if (width != cur_width || height != cur_height) {
1100       /* Set new caps according to current frame dimensions */
1101       GST_WARNING ("Output frame size has changed %dx%d -> %dx%d, updating caps",
1102           width, height, (int)cur_width, (int)cur_height);
1103       width = cur_width;
1104       height = cur_height;
1105       gst_caps_set_simple (caps,
1106         "width", G_TYPE_INT, width,
1107         "height", G_TYPE_INT, height,
1108         NULL);
1109       gst_pad_push_event (GST_BASE_SINK_PAD (baseSrc), gst_event_new_caps (caps));
1110     }
1111   }
1112
1113   *buf = gst_core_media_buffer_new (sbuf, useVideoMeta, textureCache);
1114   if (*buf == NULL) {
1115     CFRelease (sbuf);
1116     return GST_FLOW_ERROR;
1117   }
1118   CFRelease (sbuf);
1119
1120   GST_BUFFER_OFFSET (*buf) = offset++;
1121   GST_BUFFER_OFFSET_END (*buf) = GST_BUFFER_OFFSET (*buf) + 1;
1122   GST_BUFFER_TIMESTAMP (*buf) = timestamp;
1123   GST_BUFFER_DURATION (*buf) = duration;
1124
1125   if (doStats)
1126     [self updateStatistics];
1127
1128   return GST_FLOW_OK;
1129 }
1130
1131 - (GstCaps *)fixate:(GstCaps *)new_caps
1132 {
1133   GstStructure *structure;
1134
1135   new_caps = gst_caps_make_writable (new_caps);
1136   new_caps = gst_caps_truncate (new_caps);
1137   structure = gst_caps_get_structure (new_caps, 0);
1138   /* crank up to 11. This is what the presets do, but we don't use the presets
1139    * in ios >= 7.0 */
1140   gst_structure_fixate_field_nearest_int (structure, "height", G_MAXINT);
1141   gst_structure_fixate_field_nearest_fraction (structure, "framerate", 30, 1);
1142
1143   return gst_caps_fixate (new_caps);
1144 }
1145
1146 - (BOOL)decideAllocation:(GstQuery *)query
1147 {
1148   GstCaps *alloc_caps;
1149   GstCapsFeatures *features;
1150   gboolean ret;
1151
1152   ret = GST_BASE_SRC_CLASS (parent_class)->decide_allocation (baseSrc, query);
1153   if (!ret)
1154     return ret;
1155
1156   gst_query_parse_allocation (query, &alloc_caps, NULL);
1157   features = gst_caps_get_features (alloc_caps, 0);
1158   if (gst_caps_features_contains (features, GST_CAPS_FEATURE_MEMORY_GL_MEMORY)) {
1159     GstVideoTextureCacheGL *cache_gl;
1160
1161     cache_gl = textureCache ? GST_VIDEO_TEXTURE_CACHE_GL (textureCache) : NULL;
1162
1163     gst_gl_context_helper_ensure_context (ctxh);
1164     GST_INFO_OBJECT (element, "pushing textures, context %p old context %p",
1165         ctxh->context, cache_gl ? cache_gl->ctx : NULL);
1166     if (cache_gl && cache_gl->ctx != ctxh->context) {
1167       g_object_unref (textureCache);
1168       textureCache = NULL;
1169     }
1170     if (!textureCache)
1171       textureCache = gst_video_texture_cache_gl_new (ctxh->context);
1172     gst_video_texture_cache_set_format (textureCache, format, alloc_caps);
1173   }
1174
1175   return TRUE;
1176 }
1177
1178 - (void)setContext:(GstContext *)context
1179 {
1180   GST_INFO_OBJECT (element, "setting context %s",
1181           gst_context_get_context_type (context));
1182   gst_gl_handle_set_context (element, context,
1183           &ctxh->display, &ctxh->other_context);
1184   GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
1185 }
1186
1187 - (void)getSampleBuffer:(CMSampleBufferRef)sbuf
1188               timestamp:(GstClockTime *)outTimestamp
1189                duration:(GstClockTime *)outDuration
1190 {
1191   CMSampleTimingInfo time_info;
1192   GstClockTime timestamp, avf_timestamp, duration, input_clock_now, input_clock_diff, running_time;
1193   CMItemCount num_timings;
1194   GstClock *clock;
1195   CMTime now;
1196
1197   timestamp = GST_CLOCK_TIME_NONE;
1198   duration = GST_CLOCK_TIME_NONE;
1199   if (CMSampleBufferGetOutputSampleTimingInfoArray(sbuf, 1, &time_info, &num_timings) == noErr) {
1200     avf_timestamp = gst_util_uint64_scale (GST_SECOND,
1201             time_info.presentationTimeStamp.value, time_info.presentationTimeStamp.timescale);
1202
1203     if (CMTIME_IS_VALID (time_info.duration) && time_info.duration.timescale != 0)
1204       duration = gst_util_uint64_scale (GST_SECOND,
1205           time_info.duration.value, time_info.duration.timescale);
1206
1207     now = CMClockGetTime(inputClock);
1208     input_clock_now = gst_util_uint64_scale (GST_SECOND,
1209         now.value, now.timescale);
1210     input_clock_diff = input_clock_now - avf_timestamp;
1211
1212     GST_OBJECT_LOCK (element);
1213     clock = GST_ELEMENT_CLOCK (element);
1214     if (clock) {
1215       running_time = gst_clock_get_time (clock) - element->base_time;
1216       /* We use presentationTimeStamp to determine how much time it took
1217        * between capturing and receiving the frame in our delegate
1218        * (e.g. how long it spent in AVF queues), then we subtract that time
1219        * from our running time to get the actual timestamp.
1220        */
1221       if (running_time >= input_clock_diff)
1222         timestamp = running_time - input_clock_diff;
1223       else
1224         timestamp = running_time;
1225
1226       GST_DEBUG_OBJECT (element, "AVF clock: %"GST_TIME_FORMAT ", AVF PTS: %"GST_TIME_FORMAT
1227           ", AVF clock diff: %"GST_TIME_FORMAT
1228           ", running time: %"GST_TIME_FORMAT ", out PTS: %"GST_TIME_FORMAT,
1229           GST_TIME_ARGS (input_clock_now), GST_TIME_ARGS (avf_timestamp),
1230           GST_TIME_ARGS (input_clock_diff),
1231           GST_TIME_ARGS (running_time), GST_TIME_ARGS (timestamp));
1232     } else {
1233       /* no clock, can't set timestamps */
1234       timestamp = GST_CLOCK_TIME_NONE;
1235     }
1236     GST_OBJECT_UNLOCK (element);
1237   }
1238
1239   *outTimestamp = timestamp;
1240   *outDuration = duration;
1241 }
1242
1243 - (void)updateStatistics
1244 {
1245   GstClock *clock;
1246
1247   GST_OBJECT_LOCK (element);
1248   clock = GST_ELEMENT_CLOCK (element);
1249   if (clock != NULL)
1250     gst_object_ref (clock);
1251   GST_OBJECT_UNLOCK (element);
1252
1253   if (clock != NULL) {
1254     GstClockTime now = gst_clock_get_time (clock);
1255     gst_object_unref (clock);
1256
1257     count++;
1258
1259     if (GST_CLOCK_TIME_IS_VALID (lastSampling)) {
1260       if (now - lastSampling >= GST_SECOND) {
1261         GST_OBJECT_LOCK (element);
1262         fps = count;
1263         GST_OBJECT_UNLOCK (element);
1264
1265         g_object_notify (G_OBJECT (element), "fps");
1266
1267         lastSampling = now;
1268         count = 0;
1269       }
1270     } else {
1271       lastSampling = now;
1272     }
1273   }
1274 }
1275
1276 @end
1277
1278 /*
1279  * Glue code
1280  */
1281
1282 enum
1283 {
1284   PROP_0,
1285   PROP_DEVICE_INDEX,
1286   PROP_DEVICE_NAME,
1287   PROP_POSITION,
1288   PROP_ORIENTATION,
1289   PROP_DEVICE_TYPE,
1290   PROP_DO_STATS,
1291   PROP_FPS,
1292 #if !HAVE_IOS
1293   PROP_CAPTURE_SCREEN,
1294   PROP_CAPTURE_SCREEN_CURSOR,
1295   PROP_CAPTURE_SCREEN_MOUSE_CLICKS,
1296   PROP_CAPTURE_SCREEN_CROP_X,
1297   PROP_CAPTURE_SCREEN_CROP_Y,
1298   PROP_CAPTURE_SCREEN_CROP_WIDTH,
1299   PROP_CAPTURE_SCREEN_CROP_HEIGHT,
1300 #endif
1301 };
1302
1303
1304 static void gst_avf_video_src_finalize (GObject * obj);
1305 static void gst_avf_video_src_get_property (GObject * object, guint prop_id,
1306     GValue * value, GParamSpec * pspec);
1307 static void gst_avf_video_src_set_property (GObject * object, guint prop_id,
1308     const GValue * value, GParamSpec * pspec);
1309 static GstCaps * gst_avf_video_src_get_caps (GstBaseSrc * basesrc,
1310     GstCaps * filter);
1311 static gboolean gst_avf_video_src_set_caps (GstBaseSrc * basesrc,
1312     GstCaps * caps);
1313 static gboolean gst_avf_video_src_start (GstBaseSrc * basesrc);
1314 static gboolean gst_avf_video_src_stop (GstBaseSrc * basesrc);
1315 static gboolean gst_avf_video_src_query (GstBaseSrc * basesrc,
1316     GstQuery * query);
1317 static gboolean gst_avf_video_src_unlock (GstBaseSrc * basesrc);
1318 static gboolean gst_avf_video_src_unlock_stop (GstBaseSrc * basesrc);
1319 static GstFlowReturn gst_avf_video_src_create (GstPushSrc * pushsrc,
1320     GstBuffer ** buf);
1321 static GstCaps * gst_avf_video_src_fixate (GstBaseSrc * bsrc,
1322     GstCaps * caps);
1323 static gboolean gst_avf_video_src_decide_allocation (GstBaseSrc * bsrc,
1324     GstQuery * query);
1325 static void gst_avf_video_src_set_context (GstElement * element,
1326         GstContext * context);
1327
1328 static void
1329 gst_avf_video_src_class_init (GstAVFVideoSrcClass * klass)
1330 {
1331   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1332   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
1333   GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
1334   GstPushSrcClass *gstpushsrc_class = GST_PUSH_SRC_CLASS (klass);
1335
1336   gobject_class->finalize = gst_avf_video_src_finalize;
1337   gobject_class->get_property = gst_avf_video_src_get_property;
1338   gobject_class->set_property = gst_avf_video_src_set_property;
1339
1340   gstelement_class->set_context = gst_avf_video_src_set_context;
1341
1342   gstbasesrc_class->get_caps = gst_avf_video_src_get_caps;
1343   gstbasesrc_class->set_caps = gst_avf_video_src_set_caps;
1344   gstbasesrc_class->start = gst_avf_video_src_start;
1345   gstbasesrc_class->stop = gst_avf_video_src_stop;
1346   gstbasesrc_class->query = gst_avf_video_src_query;
1347   gstbasesrc_class->unlock = gst_avf_video_src_unlock;
1348   gstbasesrc_class->unlock_stop = gst_avf_video_src_unlock_stop;
1349   gstbasesrc_class->fixate = gst_avf_video_src_fixate;
1350   gstbasesrc_class->decide_allocation = gst_avf_video_src_decide_allocation;
1351
1352   gstpushsrc_class->create = gst_avf_video_src_create;
1353
1354   gst_element_class_set_metadata (gstelement_class,
1355       "Video Source (AVFoundation)", "Source/Video/Hardware",
1356       "Reads frames from an iOS/MacOS AVFoundation device",
1357       "Ole André Vadla Ravnås <oleavr@soundrop.com>");
1358
1359   gst_element_class_add_static_pad_template (gstelement_class, &src_template);
1360
1361   g_object_class_install_property (gobject_class, PROP_DEVICE_INDEX,
1362       g_param_spec_int ("device-index", "Device Index",
1363           "The zero-based device index",
1364           -1, G_MAXINT, DEFAULT_DEVICE_INDEX,
1365           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1366   g_object_class_install_property (gobject_class, PROP_DEVICE_NAME,
1367       g_param_spec_string ("device-name", "Device Name",
1368           "The name of the currently opened capture device",
1369           NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1370   g_object_class_install_property (gobject_class, PROP_POSITION,
1371                                    g_param_spec_enum ("position", "Position",
1372                                                       "The position of the capture device (front or back-facing)",
1373                                                       GST_TYPE_AVF_VIDEO_SOURCE_POSITION, DEFAULT_POSITION,
1374                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1375   g_object_class_install_property (gobject_class, PROP_ORIENTATION,
1376                                    g_param_spec_enum ("orientation", "Orientation",
1377                                                       "The orientation of the video",
1378                                                       GST_TYPE_AVF_VIDEO_SOURCE_ORIENTATION, DEFAULT_ORIENTATION,
1379                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1380   g_object_class_install_property (gobject_class, PROP_DEVICE_TYPE,
1381                                    g_param_spec_enum ("device-type", "Device Type",
1382                                                       "The general type of a video capture device",
1383                                                       GST_TYPE_AVF_VIDEO_SOURCE_DEVICE_TYPE, DEFAULT_DEVICE_TYPE,
1384                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1385   g_object_class_install_property (gobject_class, PROP_DO_STATS,
1386       g_param_spec_boolean ("do-stats", "Enable statistics",
1387           "Enable logging of statistics", DEFAULT_DO_STATS,
1388           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1389   g_object_class_install_property (gobject_class, PROP_FPS,
1390       g_param_spec_int ("fps", "Frames per second",
1391           "Last measured framerate, if statistics are enabled",
1392           -1, G_MAXINT, -1, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1393 #if !HAVE_IOS
1394   g_object_class_install_property (gobject_class, PROP_CAPTURE_SCREEN,
1395       g_param_spec_boolean ("capture-screen", "Enable screen capture",
1396           "Enable screen capture functionality", FALSE,
1397           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1398   g_object_class_install_property (gobject_class, PROP_CAPTURE_SCREEN_CURSOR,
1399       g_param_spec_boolean ("capture-screen-cursor", "Capture screen cursor",
1400           "Enable cursor capture while capturing screen", FALSE,
1401           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1402   g_object_class_install_property (gobject_class, PROP_CAPTURE_SCREEN_MOUSE_CLICKS,
1403       g_param_spec_boolean ("capture-screen-mouse-clicks", "Enable mouse clicks capture",
1404           "Enable mouse clicks capture while capturing screen", FALSE,
1405           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1406   g_object_class_install_property (gobject_class, PROP_CAPTURE_SCREEN_CROP_X,
1407       g_param_spec_uint ("screen-crop-x", "Screen capture crop X",
1408           "Horizontal coordinate of top left corner of the screen capture area",
1409           0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1410   g_object_class_install_property (gobject_class, PROP_CAPTURE_SCREEN_CROP_Y,
1411       g_param_spec_uint ("screen-crop-y", "Screen capture crop Y",
1412           "Vertical coordinate of top left corner of the screen capture area",
1413           0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1414   g_object_class_install_property (gobject_class, PROP_CAPTURE_SCREEN_CROP_WIDTH,
1415       g_param_spec_uint ("screen-crop-width", "Screen capture crop width",
1416           "Width of the screen capture area (0 = maximum)",
1417           0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1418   g_object_class_install_property (gobject_class, PROP_CAPTURE_SCREEN_CROP_HEIGHT,
1419       g_param_spec_uint ("screen-crop-height", "Screen capture crop height",
1420           "Height of the screen capture area (0 = maximum)",
1421           0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1422 #endif
1423
1424   GST_DEBUG_CATEGORY_INIT (gst_avf_video_src_debug, "avfvideosrc",
1425       0, "iOS/MacOS AVFoundation video source");
1426
1427   gst_type_mark_as_plugin_api (GST_TYPE_AVF_VIDEO_SOURCE_POSITION, 0);
1428   gst_type_mark_as_plugin_api (GST_TYPE_AVF_VIDEO_SOURCE_ORIENTATION, 0);
1429   gst_type_mark_as_plugin_api (GST_TYPE_AVF_VIDEO_SOURCE_DEVICE_TYPE, 0);
1430 }
1431
1432 static void
1433 gst_avf_video_src_init (GstAVFVideoSrc * src)
1434 {
1435   src->impl = (__bridge_retained gpointer)[[GstAVFVideoSrcImpl alloc] initWithSrc:GST_PUSH_SRC (src)];
1436 }
1437
1438 static void
1439 gst_avf_video_src_finalize (GObject * obj)
1440 {
1441   CFBridgingRelease(GST_AVF_VIDEO_SRC_CAST(obj)->impl);
1442
1443   G_OBJECT_CLASS (parent_class)->finalize (obj);
1444 }
1445
1446 static void
1447 gst_avf_video_src_get_property (GObject * object, guint prop_id, GValue * value,
1448     GParamSpec * pspec)
1449 {
1450   GstAVFVideoSrcImpl *impl = GST_AVF_VIDEO_SRC_IMPL (object);
1451
1452   switch (prop_id) {
1453 #if !HAVE_IOS
1454     case PROP_CAPTURE_SCREEN:
1455       g_value_set_boolean (value, impl.captureScreen);
1456       break;
1457     case PROP_CAPTURE_SCREEN_CURSOR:
1458       g_value_set_boolean (value, impl.captureScreenCursor);
1459       break;
1460     case PROP_CAPTURE_SCREEN_MOUSE_CLICKS:
1461       g_value_set_boolean (value, impl.captureScreenMouseClicks);
1462       break;
1463     case PROP_CAPTURE_SCREEN_CROP_X:
1464       g_value_set_uint (value, impl.cropX);
1465       break;
1466     case PROP_CAPTURE_SCREEN_CROP_Y:
1467       g_value_set_uint (value, impl.cropY);
1468       break;
1469     case PROP_CAPTURE_SCREEN_CROP_WIDTH:
1470       g_value_set_uint (value, impl.cropWidth);
1471       break;
1472     case PROP_CAPTURE_SCREEN_CROP_HEIGHT:
1473       g_value_set_uint (value, impl.cropHeight);
1474       break;
1475 #endif
1476     case PROP_DEVICE_INDEX:
1477       g_value_set_int (value, impl.deviceIndex);
1478       break;
1479     case PROP_DEVICE_NAME:
1480       g_value_set_string (value, impl.deviceName);
1481       break;
1482     case PROP_POSITION:
1483       g_value_set_enum (value, impl.position);
1484       break;
1485     case PROP_ORIENTATION:
1486       g_value_set_enum (value, impl.orientation);
1487       break;
1488     case PROP_DEVICE_TYPE:
1489       g_value_set_enum (value, impl.deviceType);
1490       break;
1491     case PROP_DO_STATS:
1492       g_value_set_boolean (value, impl.doStats);
1493       break;
1494     case PROP_FPS:
1495       GST_OBJECT_LOCK (object);
1496       g_value_set_int (value, impl.fps);
1497       GST_OBJECT_UNLOCK (object);
1498       break;
1499     default:
1500       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1501       break;
1502   }
1503 }
1504
1505 static void
1506 gst_avf_video_src_set_property (GObject * object, guint prop_id,
1507     const GValue * value, GParamSpec * pspec)
1508 {
1509   GstAVFVideoSrcImpl *impl = GST_AVF_VIDEO_SRC_IMPL (object);
1510
1511   switch (prop_id) {
1512 #if !HAVE_IOS
1513     case PROP_CAPTURE_SCREEN:
1514       impl.captureScreen = g_value_get_boolean (value);
1515       break;
1516     case PROP_CAPTURE_SCREEN_CURSOR:
1517       impl.captureScreenCursor = g_value_get_boolean (value);
1518       break;
1519     case PROP_CAPTURE_SCREEN_MOUSE_CLICKS:
1520       impl.captureScreenMouseClicks = g_value_get_boolean (value);
1521       break;
1522     case PROP_CAPTURE_SCREEN_CROP_X:
1523       impl.cropX = g_value_get_uint (value);
1524       break;
1525     case PROP_CAPTURE_SCREEN_CROP_Y:
1526       impl.cropY = g_value_get_uint (value);
1527       break;
1528     case PROP_CAPTURE_SCREEN_CROP_WIDTH:
1529       impl.cropWidth = g_value_get_uint (value);
1530       break;
1531     case PROP_CAPTURE_SCREEN_CROP_HEIGHT:
1532       impl.cropHeight = g_value_get_uint (value);
1533       break;
1534 #endif
1535     case PROP_DEVICE_INDEX:
1536       impl.deviceIndex = g_value_get_int (value);
1537       break;
1538     case PROP_POSITION:
1539       impl.position = g_value_get_enum(value);
1540       break;
1541     case PROP_ORIENTATION:
1542       impl.orientation = g_value_get_enum(value);
1543       break;
1544     case PROP_DEVICE_TYPE:
1545       impl.deviceType = g_value_get_enum(value);
1546       break;
1547     case PROP_DO_STATS:
1548       impl.doStats = g_value_get_boolean (value);
1549       break;
1550     default:
1551       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1552       break;
1553   }
1554 }
1555
1556 static GstCaps *
1557 gst_avf_video_src_get_caps (GstBaseSrc * basesrc, GstCaps * filter)
1558 {
1559   GstCaps *ret;
1560
1561   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) getCaps];
1562
1563   return ret;
1564 }
1565
1566 static gboolean
1567 gst_avf_video_src_set_caps (GstBaseSrc * basesrc, GstCaps * caps)
1568 {
1569   gboolean ret;
1570
1571   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) setCaps:caps];
1572
1573   return ret;
1574 }
1575
1576 static gboolean
1577 gst_avf_video_src_start (GstBaseSrc * basesrc)
1578 {
1579   gboolean ret;
1580
1581   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) start];
1582
1583   return ret;
1584 }
1585
1586 static gboolean
1587 gst_avf_video_src_stop (GstBaseSrc * basesrc)
1588 {
1589   gboolean ret;
1590
1591   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) stop];
1592
1593   return ret;
1594 }
1595
1596 static gboolean
1597 gst_avf_video_src_query (GstBaseSrc * basesrc, GstQuery * query)
1598 {
1599   gboolean ret;
1600
1601   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) query:query];
1602
1603   return ret;
1604 }
1605
1606 static gboolean
1607 gst_avf_video_src_unlock (GstBaseSrc * basesrc)
1608 {
1609   gboolean ret;
1610
1611   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) unlock];
1612
1613   return ret;
1614 }
1615
1616 static gboolean
1617 gst_avf_video_src_unlock_stop (GstBaseSrc * basesrc)
1618 {
1619   gboolean ret;
1620
1621   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) unlockStop];
1622
1623   return ret;
1624 }
1625
1626 static GstFlowReturn
1627 gst_avf_video_src_create (GstPushSrc * pushsrc, GstBuffer ** buf)
1628 {
1629   GstFlowReturn ret;
1630
1631   ret = [GST_AVF_VIDEO_SRC_IMPL (pushsrc) create: buf];
1632
1633   return ret;
1634 }
1635
1636
1637 static GstCaps *
1638 gst_avf_video_src_fixate (GstBaseSrc * bsrc, GstCaps * caps)
1639 {
1640   GstCaps *ret;
1641
1642   ret = [GST_AVF_VIDEO_SRC_IMPL (bsrc) fixate:caps];
1643
1644   return ret;
1645 }
1646
1647 static gboolean
1648 gst_avf_video_src_decide_allocation (GstBaseSrc * bsrc,
1649     GstQuery * query)
1650 {
1651   gboolean ret;
1652
1653   ret = [GST_AVF_VIDEO_SRC_IMPL (bsrc) decideAllocation:query];
1654
1655   return ret;
1656 }
1657
1658 static void
1659 gst_avf_video_src_set_context (GstElement * element, GstContext * context)
1660 {
1661   [GST_AVF_VIDEO_SRC_IMPL (element) setContext:context];
1662 }
1663
1664 GstCaps*
1665 gst_av_capture_device_get_caps (AVCaptureDevice *device, AVCaptureVideoDataOutput *output, GstAVFVideoSourceOrientation orientation)
1666 {
1667   NSArray *formats = [device valueForKey:@"formats"];
1668   NSArray *pixel_formats = output.availableVideoCVPixelFormatTypes;
1669   GstCaps *result_caps, *result_gl_caps;
1670 #if !HAVE_IOS
1671   GstVideoFormat gl_format = GST_VIDEO_FORMAT_UYVY;
1672 #else
1673   GstVideoFormat gl_format = GST_VIDEO_FORMAT_NV12;
1674 #endif
1675
1676   result_caps = gst_caps_new_empty ();
1677   result_gl_caps = gst_caps_new_empty ();
1678
1679   /* Do not use AVCaptureDeviceFormat or AVFrameRateRange only
1680    * available in iOS >= 7.0. We use a dynamic approach with key-value
1681    * coding or performSelector */
1682   for (NSObject *f in [formats reverseObjectEnumerator]) {
1683     /* formatDescription can't be retrieved with valueForKey so use a selector here */
1684     CMFormatDescriptionRef formatDescription = (__bridge CMFormatDescriptionRef) [f performSelector:@selector(formatDescription)];
1685     CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions (formatDescription);
1686     dimensions = get_oriented_dimensions (orientation, dimensions);
1687
1688     for (NSObject *rate in [f valueForKey:@"videoSupportedFrameRateRanges"]) {
1689       int min_fps_n, min_fps_d, max_fps_n, max_fps_d;
1690       gdouble min_fps, max_fps;
1691
1692       [[rate valueForKey:@"minFrameRate"] getValue:&min_fps];
1693       gst_util_double_to_fraction (min_fps, &min_fps_n, &min_fps_d);
1694
1695       [[rate valueForKey:@"maxFrameRate"] getValue:&max_fps];
1696       gst_util_double_to_fraction (max_fps, &max_fps_n, &max_fps_d);
1697
1698       for (NSNumber *pixel_format in pixel_formats) {
1699         GstVideoFormat gst_format = get_gst_video_format (pixel_format);
1700
1701         if (gst_format != GST_VIDEO_FORMAT_UNKNOWN) {
1702           if (min_fps != max_fps)
1703             gst_caps_append (result_caps, GST_AVF_FPS_RANGE_CAPS_NEW (gst_format, dimensions.width, dimensions.height, min_fps_n, min_fps_d, max_fps_n, max_fps_d));
1704           else
1705             gst_caps_append (result_caps, GST_AVF_CAPS_NEW (gst_format, dimensions.width, dimensions.height, max_fps_n, max_fps_d));
1706         }
1707
1708         if (gst_format == gl_format) {
1709           GstCaps *gl_caps;
1710           if (min_fps != max_fps) {
1711             gl_caps = GST_AVF_FPS_RANGE_CAPS_NEW (gl_format,
1712                                                   dimensions.width, dimensions.height,
1713                                                   min_fps_n, min_fps_d,
1714                                                   max_fps_n, max_fps_d);
1715           } else {
1716             gl_caps = GST_AVF_CAPS_NEW (gl_format,
1717                                         dimensions.width, dimensions.height,
1718                                         max_fps_n, max_fps_d);
1719           }
1720           gst_caps_set_features (gl_caps, 0,
1721                                  gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
1722                                                         NULL));
1723           gst_caps_set_simple (gl_caps,
1724                                "texture-target", G_TYPE_STRING,
1725 #if !HAVE_IOS
1726                                GST_GL_TEXTURE_TARGET_RECTANGLE_STR,
1727 #else
1728                                GST_GL_TEXTURE_TARGET_2D_STR,
1729 #endif
1730                                NULL);
1731           gst_caps_append (result_gl_caps, gl_caps);
1732         }
1733       }
1734     }
1735   }
1736
1737   result_gl_caps = gst_caps_simplify (gst_caps_merge (result_gl_caps, result_caps));
1738
1739   return result_gl_caps;
1740 }
1741
1742 static GstVideoFormat
1743 get_gst_video_format (NSNumber *pixel_format)
1744 {
1745   GstVideoFormat gst_format = GST_VIDEO_FORMAT_UNKNOWN;
1746
1747   switch ([pixel_format integerValue]) {
1748     case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: /* 420v */
1749       gst_format = GST_VIDEO_FORMAT_NV12;
1750       break;
1751     case kCVPixelFormatType_422YpCbCr8: /* 2vuy */
1752       gst_format = GST_VIDEO_FORMAT_UYVY;
1753       break;
1754     case kCVPixelFormatType_32BGRA: /* BGRA */
1755       gst_format = GST_VIDEO_FORMAT_BGRA;
1756       break;
1757     case kCVPixelFormatType_422YpCbCr8_yuvs: /* yuvs */
1758       gst_format = GST_VIDEO_FORMAT_YUY2;
1759       break;
1760     default:
1761       break;
1762   }
1763
1764   return gst_format;
1765 }
1766
1767 static CMVideoDimensions
1768 get_oriented_dimensions (GstAVFVideoSourceOrientation orientation, CMVideoDimensions dimensions)
1769 {
1770   CMVideoDimensions orientedDimensions;
1771   if (orientation == GST_AVF_VIDEO_SOURCE_ORIENTATION_PORTRAIT_UPSIDE_DOWN ||
1772       orientation == GST_AVF_VIDEO_SOURCE_ORIENTATION_PORTRAIT) {
1773     orientedDimensions.width = dimensions.height;
1774     orientedDimensions.height = dimensions.width;
1775   } else {
1776     orientedDimensions = dimensions;
1777   }
1778   return orientedDimensions;
1779 }