applemedia: avfvideosrc: do fixate
[platform/upstream/gstreamer.git] / sys / applemedia / avfvideosrc.m
1 /*
2  * Copyright (C) 2010 Ole André Vadla Ravnås <oleavr@soundrop.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "avfvideosrc.h"
25
26 #import <AVFoundation/AVFoundation.h>
27 #if !HAVE_IOS
28 #import <AppKit/AppKit.h>
29 #endif
30 #include <gst/video/video.h>
31 #include <gst/gl/gstglcontext.h>
32 #include "coremediabuffer.h"
33 #include "videotexturecache.h"
34
35 #define DEFAULT_DEVICE_INDEX  -1
36 #define DEFAULT_DO_STATS      FALSE
37
38 #define DEVICE_FPS_N          25
39 #define DEVICE_FPS_D          1
40
41 #define BUFFER_QUEUE_SIZE     2
42
43 GST_DEBUG_CATEGORY (gst_avf_video_src_debug);
44 #define GST_CAT_DEFAULT gst_avf_video_src_debug
45
46 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
47     GST_PAD_SRC,
48     GST_PAD_ALWAYS,
49     GST_STATIC_CAPS (
50 #if !HAVE_IOS
51         GST_VIDEO_CAPS_MAKE_WITH_FEATURES
52         (GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
53             "UYVY") ", "
54         "texture-target = " GST_GL_TEXTURE_TARGET_RECTANGLE_STR ";"
55 #else
56         GST_VIDEO_CAPS_MAKE_WITH_FEATURES
57         (GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
58             "BGRA") ", "
59         "texture-target = " GST_GL_TEXTURE_TARGET_2D_STR "; "
60 #endif
61         "video/x-raw, "
62         "format = (string) { NV12, UYVY, YUY2 }, "
63         "framerate = " GST_VIDEO_FPS_RANGE ", "
64         "width = " GST_VIDEO_SIZE_RANGE ", "
65         "height = " GST_VIDEO_SIZE_RANGE "; "
66
67         "video/x-raw, "
68         "format = (string) BGRA, "
69         "framerate = " GST_VIDEO_FPS_RANGE ", "
70         "width = " GST_VIDEO_SIZE_RANGE ", "
71         "height = " GST_VIDEO_SIZE_RANGE "; "
72 ));
73
74 typedef enum _QueueState {
75   NO_BUFFERS = 1,
76   HAS_BUFFER_OR_STOP_REQUEST,
77 } QueueState;
78
79 #define gst_avf_video_src_parent_class parent_class
80 G_DEFINE_TYPE (GstAVFVideoSrc, gst_avf_video_src, GST_TYPE_PUSH_SRC);
81
82 @interface GstAVFVideoSrcImpl : NSObject <AVCaptureVideoDataOutputSampleBufferDelegate> {
83   GstElement *element;
84   GstBaseSrc *baseSrc;
85   GstPushSrc *pushSrc;
86
87   gint deviceIndex;
88   BOOL doStats;
89
90   AVCaptureSession *session;
91   AVCaptureInput *input;
92   AVCaptureVideoDataOutput *output;
93   AVCaptureDevice *device;
94   AVCaptureConnection *connection;
95   CMClockRef inputClock;
96
97   dispatch_queue_t mainQueue;
98   dispatch_queue_t workerQueue;
99   NSConditionLock *bufQueueLock;
100   NSMutableArray *bufQueue;
101   BOOL stopRequest;
102
103   GstCaps *caps;
104   GstVideoFormat format;
105   gint width, height;
106   GstClockTime latency;
107   guint64 offset;
108
109   GstClockTime lastSampling;
110   guint count;
111   gint fps;
112   BOOL captureScreen;
113   BOOL captureScreenCursor;
114   BOOL captureScreenMouseClicks;
115
116   BOOL useVideoMeta;
117   GstVideoTextureCache *textureCache;
118 }
119
120 - (id)init;
121 - (id)initWithSrc:(GstPushSrc *)src;
122 - (void)finalize;
123
124 @property int deviceIndex;
125 @property BOOL doStats;
126 @property int fps;
127 @property BOOL captureScreen;
128 @property BOOL captureScreenCursor;
129 @property BOOL captureScreenMouseClicks;
130
131 - (BOOL)openScreenInput;
132 - (BOOL)openDeviceInput;
133 - (BOOL)openDevice;
134 - (void)closeDevice;
135 - (GstVideoFormat)getGstVideoFormat:(NSNumber *)pixel_format;
136 #if !HAVE_IOS
137 - (CGDirectDisplayID)getDisplayIdFromDeviceIndex;
138 #endif
139 - (GstCaps *)getDeviceCaps;
140 - (BOOL)setDeviceCaps:(GstVideoInfo *)info;
141 - (BOOL)getSessionPresetCaps:(GstCaps *)result;
142 - (BOOL)setSessionPresetCaps:(GstVideoInfo *)info;
143 - (GstCaps *)getCaps;
144 - (BOOL)setCaps:(GstCaps *)new_caps;
145 - (BOOL)start;
146 - (BOOL)stop;
147 - (BOOL)unlock;
148 - (BOOL)unlockStop;
149 - (BOOL)query:(GstQuery *)query;
150 - (GstStateChangeReturn)changeState:(GstStateChange)transition;
151 - (GstFlowReturn)create:(GstBuffer **)buf;
152 - (GstCaps *)fixate:(GstCaps *)caps;
153 - (void)updateStatistics;
154 - (void)captureOutput:(AVCaptureOutput *)captureOutput
155 didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
156        fromConnection:(AVCaptureConnection *)connection;
157
158 @end
159
160 @implementation GstAVFVideoSrcImpl
161
162 @synthesize deviceIndex, doStats, fps, captureScreen,
163             captureScreenCursor, captureScreenMouseClicks;
164
165 - (id)init
166 {
167   return [self initWithSrc:NULL];
168 }
169
170 - (id)initWithSrc:(GstPushSrc *)src
171 {
172   if ((self = [super init])) {
173     element = GST_ELEMENT_CAST (src);
174     baseSrc = GST_BASE_SRC_CAST (src);
175     pushSrc = src;
176
177     deviceIndex = DEFAULT_DEVICE_INDEX;
178     captureScreen = NO;
179     captureScreenCursor = NO;
180     captureScreenMouseClicks = NO;
181     useVideoMeta = NO;
182     textureCache = NULL;
183
184     mainQueue =
185         dispatch_queue_create ("org.freedesktop.gstreamer.avfvideosrc.main", NULL);
186     workerQueue =
187         dispatch_queue_create ("org.freedesktop.gstreamer.avfvideosrc.output", NULL);
188
189     gst_base_src_set_live (baseSrc, TRUE);
190     gst_base_src_set_format (baseSrc, GST_FORMAT_TIME);
191   }
192
193   return self;
194 }
195
196 - (void)finalize
197 {
198   dispatch_release (mainQueue);
199   mainQueue = NULL;
200   dispatch_release (workerQueue);
201   workerQueue = NULL;
202
203   [super finalize];
204 }
205
206 - (BOOL)openDeviceInput
207 {
208   NSString *mediaType = AVMediaTypeVideo;
209   NSError *err;
210
211   if (deviceIndex == DEFAULT_DEVICE_INDEX) {
212     device = [AVCaptureDevice defaultDeviceWithMediaType:mediaType];
213     if (device == nil) {
214       GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
215                           ("No video capture devices found"), (NULL));
216       return NO;
217     }
218   } else {
219     NSArray *devices = [AVCaptureDevice devicesWithMediaType:mediaType];
220     if (deviceIndex >= [devices count]) {
221       GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
222                           ("Invalid video capture device index"), (NULL));
223       return NO;
224     }
225     device = [devices objectAtIndex:deviceIndex];
226   }
227   g_assert (device != nil);
228   [device retain];
229
230   GST_INFO ("Opening '%s'", [[device localizedName] UTF8String]);
231
232   input = [AVCaptureDeviceInput deviceInputWithDevice:device
233                                                 error:&err];
234   if (input == nil) {
235     GST_ELEMENT_ERROR (element, RESOURCE, BUSY,
236         ("Failed to open device: %s",
237         [[err localizedDescription] UTF8String]),
238         (NULL));
239     [device release];
240     device = nil;
241     return NO;
242   }
243   [input retain];
244   return YES;
245 }
246
247 - (BOOL)openScreenInput
248 {
249 #if HAVE_IOS
250   return NO;
251 #else
252   CGDirectDisplayID displayId;
253
254   GST_DEBUG_OBJECT (element, "Opening screen input");
255
256   displayId = [self getDisplayIdFromDeviceIndex];
257   if (displayId == 0)
258     return NO;
259
260   AVCaptureScreenInput *screenInput =
261       [[AVCaptureScreenInput alloc] initWithDisplayID:displayId];
262
263
264   @try {
265     [screenInput setValue:[NSNumber numberWithBool:captureScreenCursor]
266                  forKey:@"capturesCursor"];
267
268   } @catch (NSException *exception) {
269     if (![[exception name] isEqualToString:NSUndefinedKeyException]) {
270       GST_WARNING ("An unexpected error occured: %s",
271                    [[exception reason] UTF8String]);
272     }
273     GST_WARNING ("Capturing cursor is only supported in OS X >= 10.8");
274   }
275   screenInput.capturesMouseClicks = captureScreenMouseClicks;
276   input = screenInput;
277   [input retain];
278   return YES;
279 #endif
280 }
281
282 - (BOOL)openDevice
283 {
284   BOOL success = NO, *successPtr = &success;
285
286   GST_DEBUG_OBJECT (element, "Opening device");
287
288   dispatch_sync (mainQueue, ^{
289     BOOL ret;
290
291     if (captureScreen)
292       ret = [self openScreenInput];
293     else
294       ret = [self openDeviceInput];
295
296     if (!ret)
297       return;
298
299     output = [[AVCaptureVideoDataOutput alloc] init];
300     [output setSampleBufferDelegate:self
301                               queue:workerQueue];
302     output.alwaysDiscardsLateVideoFrames = YES;
303     output.videoSettings = nil; /* device native format */
304
305     session = [[AVCaptureSession alloc] init];
306     [session addInput:input];
307     [session addOutput:output];
308
309     /* retained by session */
310     connection = [[output connections] firstObject];
311     inputClock = ((AVCaptureInputPort *)connection.inputPorts[0]).clock;
312
313     *successPtr = YES;
314   });
315
316   GST_DEBUG_OBJECT (element, "Opening device %s", success ? "succeed" : "failed");
317
318   return success;
319 }
320
321 - (void)closeDevice
322 {
323   GST_DEBUG_OBJECT (element, "Closing device");
324
325   dispatch_sync (mainQueue, ^{
326     g_assert (![session isRunning]);
327
328     connection = nil;
329     inputClock = nil;
330
331     [session removeInput:input];
332     [session removeOutput:output];
333
334     [session release];
335     session = nil;
336
337     [input release];
338     input = nil;
339
340     [output release];
341     output = nil;
342
343     if (!captureScreen) {
344       [device release];
345       device = nil;
346     }
347
348     if (caps)
349       gst_caps_unref (caps);
350     caps = NULL;
351   });
352 }
353
354 #define GST_AVF_CAPS_NEW(format, w, h, fps_n, fps_d)                  \
355     (gst_caps_new_simple ("video/x-raw",                              \
356         "width", G_TYPE_INT, w,                                       \
357         "height", G_TYPE_INT, h,                                      \
358         "format", G_TYPE_STRING, gst_video_format_to_string (format), \
359         "framerate", GST_TYPE_FRACTION, (fps_n), (fps_d),             \
360         NULL))
361
362 #define GST_AVF_FPS_RANGE_CAPS_NEW(format, w, h, min_fps_n, min_fps_d, max_fps_n, max_fps_d) \
363     (gst_caps_new_simple ("video/x-raw",                              \
364         "width", G_TYPE_INT, w,                                       \
365         "height", G_TYPE_INT, h,                                      \
366         "format", G_TYPE_STRING, gst_video_format_to_string (format), \
367         "framerate", GST_TYPE_FRACTION_RANGE, (min_fps_n), (min_fps_d), (max_fps_n), (max_fps_d), \
368         NULL))
369
370 - (GstVideoFormat)getGstVideoFormat:(NSNumber *)pixel_format
371 {
372   GstVideoFormat gst_format = GST_VIDEO_FORMAT_UNKNOWN;
373
374   switch ([pixel_format integerValue]) {
375   case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: /* 420v */
376     gst_format = GST_VIDEO_FORMAT_NV12;
377     break;
378   case kCVPixelFormatType_422YpCbCr8: /* 2vuy */
379     gst_format = GST_VIDEO_FORMAT_UYVY;
380     break;
381   case kCVPixelFormatType_32BGRA: /* BGRA */
382     gst_format = GST_VIDEO_FORMAT_BGRA;
383     break;
384   case kCVPixelFormatType_422YpCbCr8_yuvs: /* yuvs */
385     gst_format = GST_VIDEO_FORMAT_YUY2;
386     break;
387   default:
388     GST_LOG_OBJECT (element, "Pixel format %s is not handled by avfvideosrc",
389         [[pixel_format stringValue] UTF8String]);
390     break;
391   }
392
393   return gst_format;
394 }
395
396 #if !HAVE_IOS
397 - (CGDirectDisplayID)getDisplayIdFromDeviceIndex
398 {
399   NSDictionary *description;
400   NSNumber *displayId;
401   NSArray *screens = [NSScreen screens];
402
403   if (deviceIndex == DEFAULT_DEVICE_INDEX)
404     return kCGDirectMainDisplay;
405   if (deviceIndex >= [screens count]) {
406     GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
407                         ("Invalid screen capture device index"), (NULL));
408     return 0;
409   }
410   description = [[screens objectAtIndex:deviceIndex] deviceDescription];
411   displayId = [description objectForKey:@"NSScreenNumber"];
412   return [displayId unsignedIntegerValue];
413 }
414 #endif
415
416 - (GstCaps *)getDeviceCaps
417 {
418   NSArray *formats = [device valueForKey:@"formats"];
419   NSArray *pixel_formats = output.availableVideoCVPixelFormatTypes;
420   GstCaps *result_caps, *result_gl_caps;
421 #if !HAVE_IOS
422   GstVideoFormat gl_format = GST_VIDEO_FORMAT_UYVY;
423 #else
424   GstVideoFormat gl_format = GST_VIDEO_FORMAT_BGRA;
425 #endif
426
427   GST_DEBUG_OBJECT (element, "Getting device caps");
428
429   result_caps = gst_caps_new_empty ();
430   result_gl_caps = gst_caps_new_empty ();
431
432   /* Do not use AVCaptureDeviceFormat or AVFrameRateRange only
433    * available in iOS >= 7.0. We use a dynamic approach with key-value
434    * coding or performSelector */
435   for (NSObject *f in [formats reverseObjectEnumerator]) {
436     CMFormatDescriptionRef formatDescription;
437     CMVideoDimensions dimensions;
438
439     /* formatDescription can't be retrieved with valueForKey so use a selector here */
440     formatDescription = (CMFormatDescriptionRef) [f performSelector:@selector(formatDescription)];
441     dimensions = CMVideoFormatDescriptionGetDimensions(formatDescription);
442     for (NSObject *rate in [f valueForKey:@"videoSupportedFrameRateRanges"]) {
443       int min_fps_n, min_fps_d, max_fps_n, max_fps_d;
444       gdouble min_fps, max_fps;
445
446       [[rate valueForKey:@"minFrameRate"] getValue:&min_fps];
447       gst_util_double_to_fraction (min_fps, &min_fps_n, &min_fps_d);
448
449       [[rate valueForKey:@"maxFrameRate"] getValue:&max_fps];
450       gst_util_double_to_fraction (max_fps, &max_fps_n, &max_fps_d);
451
452       for (NSNumber *pixel_format in pixel_formats) {
453         GstVideoFormat gst_format = [self getGstVideoFormat:pixel_format];
454
455         if (gst_format != GST_VIDEO_FORMAT_UNKNOWN) {
456           if (min_fps != max_fps)
457             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));
458           else
459             gst_caps_append (result_caps, GST_AVF_CAPS_NEW (gst_format, dimensions.width, dimensions.height, max_fps_n, max_fps_d));
460         }
461
462         if (gst_format == gl_format) {
463           GstCaps *gl_caps;
464           if (min_fps != max_fps) {
465             gl_caps = GST_AVF_FPS_RANGE_CAPS_NEW (gl_format,
466                     dimensions.width, dimensions.height,
467                     min_fps_n, min_fps_d,
468                     max_fps_n, max_fps_d);
469           } else {
470             gl_caps = GST_AVF_CAPS_NEW (gl_format,
471                     dimensions.width, dimensions.height,
472                     max_fps_n, max_fps_d);
473           }
474           gst_caps_set_features (gl_caps, 0,
475                   gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
476                       NULL));
477           gst_caps_set_simple (gl_caps,
478                   "texture-target", G_TYPE_STRING,
479 #if !HAVE_IOS
480                   GST_GL_TEXTURE_TARGET_RECTANGLE_STR,
481 #else
482                   GST_GL_TEXTURE_TARGET_2D_STR,
483 #endif
484                   NULL);
485           gst_caps_append (result_gl_caps, gl_caps);
486         }
487       }
488     }
489   }
490
491   result_gl_caps = gst_caps_simplify (gst_caps_merge (result_gl_caps, result_caps));
492
493   GST_INFO_OBJECT (element, "Device returned the following caps %" GST_PTR_FORMAT, result_gl_caps);
494
495   return result_gl_caps;
496 }
497
498 - (BOOL)setDeviceCaps:(GstVideoInfo *)info
499 {
500   double framerate;
501   gboolean found_format = FALSE, found_framerate = FALSE;
502   NSArray *formats = [device valueForKey:@"formats"];
503   gst_util_fraction_to_double (info->fps_n, info->fps_d, &framerate);
504
505   GST_DEBUG_OBJECT (element, "Setting device caps");
506
507   if ([device lockForConfiguration:NULL] == YES) {
508     for (NSObject *f in formats) {
509       CMFormatDescriptionRef formatDescription;
510       CMVideoDimensions dimensions;
511
512       formatDescription = (CMFormatDescriptionRef) [f performSelector:@selector(formatDescription)];
513       dimensions = CMVideoFormatDescriptionGetDimensions(formatDescription);
514       if (dimensions.width == info->width && dimensions.height == info->height) {
515         found_format = TRUE;
516         [device setValue:f forKey:@"activeFormat"];
517         for (NSObject *rate in [f valueForKey:@"videoSupportedFrameRateRanges"]) {
518           gdouble min_frame_rate, max_frame_rate;
519
520           [[rate valueForKey:@"minFrameRate"] getValue:&min_frame_rate];
521           [[rate valueForKey:@"maxFrameRate"] getValue:&max_frame_rate];
522           if ((framerate >= min_frame_rate - 0.00001) &&
523               (framerate <= max_frame_rate + 0.00001)) {
524             NSValue *min_frame_duration, *max_frame_duration;
525
526             found_framerate = TRUE;
527             min_frame_duration = [rate valueForKey:@"minFrameDuration"];
528             max_frame_duration = [rate valueForKey:@"maxFrameDuration"];
529             [device setValue:min_frame_duration forKey:@"activeVideoMinFrameDuration"];
530             @try {
531               /* Only available on OSX >= 10.8 and iOS >= 7.0 */
532               // Restrict activeVideoMaxFrameDuration to the minimum value so we get a better capture frame rate
533               [device setValue:min_frame_duration forKey:@"activeVideoMaxFrameDuration"];
534             } @catch (NSException *exception) {
535               if (![[exception name] isEqualToString:NSUndefinedKeyException]) {
536                 GST_WARNING ("An unexcepted error occured: %s",
537                               [exception.reason UTF8String]);
538               }
539             }
540             break;
541           }
542         }
543       }
544     }
545     if (!found_format) {
546       GST_WARNING ("Unsupported capture dimensions %dx%d", info->width, info->height);
547       return NO;
548     }
549     if (!found_framerate) {
550       GST_WARNING ("Unsupported capture framerate %d/%d", info->fps_n, info->fps_d);
551       return NO;
552     }
553   } else {
554     GST_WARNING ("Couldn't lock device for configuration");
555     return NO;
556   }
557   return YES;
558 }
559
560 - (BOOL)getSessionPresetCaps:(GstCaps *)result
561 {
562   NSArray *pixel_formats = output.availableVideoCVPixelFormatTypes;
563   for (NSNumber *pixel_format in pixel_formats) {
564     GstVideoFormat gst_format = [self getGstVideoFormat:pixel_format];
565     if (gst_format == GST_VIDEO_FORMAT_UNKNOWN)
566       continue;
567
568 #if HAVE_IOS
569     if ([session canSetSessionPreset:AVCaptureSessionPreset1920x1080])
570       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 1920, 1080, DEVICE_FPS_N, DEVICE_FPS_D));
571 #endif
572     if ([session canSetSessionPreset:AVCaptureSessionPreset1280x720])
573       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 1280, 720, DEVICE_FPS_N, DEVICE_FPS_D));
574     if ([session canSetSessionPreset:AVCaptureSessionPreset640x480])
575       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 640, 480, DEVICE_FPS_N, DEVICE_FPS_D));
576     if ([session canSetSessionPreset:AVCaptureSessionPresetMedium])
577       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 480, 360, DEVICE_FPS_N, DEVICE_FPS_D));
578     if ([session canSetSessionPreset:AVCaptureSessionPreset352x288])
579       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 352, 288, DEVICE_FPS_N, DEVICE_FPS_D));
580     if ([session canSetSessionPreset:AVCaptureSessionPresetLow])
581       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 192, 144, DEVICE_FPS_N, DEVICE_FPS_D));
582   }
583
584   GST_LOG_OBJECT (element, "Session presets returned the following caps %" GST_PTR_FORMAT, result);
585
586   return YES;
587 }
588
589 - (BOOL)setSessionPresetCaps:(GstVideoInfo *)info;
590 {
591   GST_DEBUG_OBJECT (element, "Setting session presset caps");
592
593   if ([device lockForConfiguration:NULL] != YES) {
594     GST_WARNING ("Couldn't lock device for configuration");
595     return NO;
596   }
597
598   switch (info->width) {
599   case 192:
600     session.sessionPreset = AVCaptureSessionPresetLow;
601     break;
602   case 352:
603     session.sessionPreset = AVCaptureSessionPreset352x288;
604     break;
605   case 480:
606     session.sessionPreset = AVCaptureSessionPresetMedium;
607     break;
608   case 640:
609     session.sessionPreset = AVCaptureSessionPreset640x480;
610     break;
611   case 1280:
612     session.sessionPreset = AVCaptureSessionPreset1280x720;
613     break;
614 #if HAVE_IOS
615   case 1920:
616     session.sessionPreset = AVCaptureSessionPreset1920x1080;
617     break;
618 #endif
619   default:
620     GST_WARNING ("Unsupported capture dimensions %dx%d", info->width, info->height);
621     return NO;
622   }
623   return YES;
624 }
625
626 - (GstCaps *)getCaps
627 {
628   GstCaps *result;
629   NSArray *pixel_formats;
630
631   if (session == nil)
632     return NULL; /* BaseSrc will return template caps */
633
634   result = gst_caps_new_empty ();
635   pixel_formats = output.availableVideoCVPixelFormatTypes;
636
637   if (captureScreen) {
638 #if !HAVE_IOS
639     CGRect rect = CGDisplayBounds ([self getDisplayIdFromDeviceIndex]);
640     for (NSNumber *pixel_format in pixel_formats) {
641       GstVideoFormat gst_format = [self getGstVideoFormat:pixel_format];
642       if (gst_format != GST_VIDEO_FORMAT_UNKNOWN)
643         gst_caps_append (result, gst_caps_new_simple ("video/x-raw",
644             "width", G_TYPE_INT, (int)rect.size.width,
645             "height", G_TYPE_INT, (int)rect.size.height,
646             "format", G_TYPE_STRING, gst_video_format_to_string (gst_format),
647             NULL));
648     }
649 #else
650     GST_WARNING ("Screen capture is not supported by iOS");
651 #endif
652     return result;
653   }
654
655   @try {
656     result = gst_caps_merge (result, [self getDeviceCaps]);
657   } @catch (NSException *exception) {
658     if (![[exception name] isEqualToString:NSUndefinedKeyException]) {
659       GST_WARNING ("An unexcepted error occured: %s", [exception.reason UTF8String]);
660       return result;
661     }
662
663     /* Fallback on session presets API for iOS < 7.0 */
664     [self getSessionPresetCaps:result];
665   }
666
667   return result;
668 }
669
670 - (BOOL)setCaps:(GstCaps *)new_caps
671 {
672   GstVideoInfo info;
673   BOOL success = YES, *successPtr = &success;
674
675   gst_video_info_init (&info);
676   gst_video_info_from_caps (&info, new_caps);
677
678   width = info.width;
679   height = info.height;
680   format = info.finfo->format;
681   latency = gst_util_uint64_scale (GST_SECOND, info.fps_d, info.fps_n);
682
683   dispatch_sync (mainQueue, ^{
684     int newformat;
685
686     g_assert (![session isRunning]);
687
688     if (captureScreen) {
689 #if !HAVE_IOS
690       AVCaptureScreenInput *screenInput = (AVCaptureScreenInput *)input;
691       screenInput.minFrameDuration = CMTimeMake(info.fps_d, info.fps_n);
692 #else
693       GST_WARNING ("Screen capture is not supported by iOS");
694       *successPtr = NO;
695       return;
696 #endif
697     } else {
698       @try {
699
700         /* formats and activeFormat keys are only available on OSX >= 10.7 and iOS >= 7.0 */
701         *successPtr = [self setDeviceCaps:(GstVideoInfo *)&info];
702         if (*successPtr != YES)
703           return;
704
705       } @catch (NSException *exception) {
706
707         if (![[exception name] isEqualToString:NSUndefinedKeyException]) {
708           GST_WARNING ("An unexcepted error occured: %s", [exception.reason UTF8String]);
709           *successPtr = NO;
710           return;
711         }
712
713         /* Fallback on session presets API for iOS < 7.0 */
714         *successPtr = [self setSessionPresetCaps:(GstVideoInfo *)&info];
715         if (*successPtr != YES)
716           return;
717       }
718     }
719
720     switch (format) {
721       case GST_VIDEO_FORMAT_NV12:
722         newformat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
723         break;
724       case GST_VIDEO_FORMAT_UYVY:
725         newformat = kCVPixelFormatType_422YpCbCr8;
726         break;
727       case GST_VIDEO_FORMAT_YUY2:
728         newformat = kCVPixelFormatType_422YpCbCr8_yuvs;
729         break;
730       case GST_VIDEO_FORMAT_BGRA:
731         newformat = kCVPixelFormatType_32BGRA;
732         break;
733       default:
734         *successPtr = NO;
735         GST_WARNING ("Unsupported output format %s",
736             gst_video_format_to_string (format));
737         return;
738     }
739
740     GST_INFO_OBJECT (element,
741         "width: %d height: %d format: %s", width, height,
742         gst_video_format_to_string (format));
743
744     output.videoSettings = [NSDictionary
745         dictionaryWithObject:[NSNumber numberWithInt:newformat]
746         forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey];
747
748     if (caps)
749       gst_caps_unref (caps);
750     caps = gst_caps_copy (new_caps);
751
752     if (textureCache)
753       gst_video_texture_cache_free (textureCache);
754     textureCache = NULL;
755
756     GstCapsFeatures *features = gst_caps_get_features (caps, 0);
757     if (gst_caps_features_contains (features, GST_CAPS_FEATURE_MEMORY_GL_MEMORY)) {
758       GstGLContext *context = query_gl_context (GST_BASE_SRC_PAD (baseSrc));
759       textureCache = gst_video_texture_cache_new (context);
760       gst_video_texture_cache_set_format (textureCache, format, caps);
761       gst_object_unref (context);
762     }
763
764     GST_INFO_OBJECT (element, "configured caps %"GST_PTR_FORMAT
765         ", pushing textures %d", caps, textureCache != NULL);
766
767     [session startRunning];
768
769     /* Unlock device configuration only after session is started so the session
770      * won't reset the capture formats */
771     [device unlockForConfiguration];
772   });
773
774   return success;
775 }
776
777 - (BOOL)start
778 {
779   bufQueueLock = [[NSConditionLock alloc] initWithCondition:NO_BUFFERS];
780   bufQueue = [[NSMutableArray alloc] initWithCapacity:BUFFER_QUEUE_SIZE];
781   stopRequest = NO;
782
783   offset = 0;
784   latency = GST_CLOCK_TIME_NONE;
785
786   lastSampling = GST_CLOCK_TIME_NONE;
787   count = 0;
788   fps = -1;
789
790   return YES;
791 }
792
793 - (BOOL)stop
794 {
795   dispatch_sync (mainQueue, ^{ [session stopRunning]; });
796   dispatch_sync (workerQueue, ^{});
797
798   [bufQueueLock release];
799   bufQueueLock = nil;
800   [bufQueue release];
801   bufQueue = nil;
802
803   if (textureCache)
804       gst_video_texture_cache_free (textureCache);
805   textureCache = NULL;
806
807   return YES;
808 }
809
810 - (BOOL)query:(GstQuery *)query
811 {
812   BOOL result = NO;
813
814   if (GST_QUERY_TYPE (query) == GST_QUERY_LATENCY) {
815     if (device != nil && caps != NULL) {
816       GstClockTime min_latency, max_latency;
817
818       min_latency = max_latency = latency;
819       result = YES;
820
821       GST_DEBUG_OBJECT (element, "reporting latency of min %" GST_TIME_FORMAT
822           " max %" GST_TIME_FORMAT,
823           GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency));
824       gst_query_set_latency (query, TRUE, min_latency, max_latency);
825     }
826   } else {
827     result = GST_BASE_SRC_CLASS (parent_class)->query (baseSrc, query);
828   }
829
830   return result;
831 }
832
833 - (BOOL)unlock
834 {
835   [bufQueueLock lock];
836   stopRequest = YES;
837   [bufQueueLock unlockWithCondition:HAS_BUFFER_OR_STOP_REQUEST];
838
839   return YES;
840 }
841
842 - (BOOL)unlockStop
843 {
844   [bufQueueLock lock];
845   stopRequest = NO;
846   [bufQueueLock unlockWithCondition:([bufQueue count] == 0) ? NO_BUFFERS : HAS_BUFFER_OR_STOP_REQUEST];
847
848   return YES;
849 }
850
851 - (GstStateChangeReturn)changeState:(GstStateChange)transition
852 {
853   GstStateChangeReturn ret;
854
855   if (transition == GST_STATE_CHANGE_NULL_TO_READY) {
856     if (![self openDevice])
857       return GST_STATE_CHANGE_FAILURE;
858   }
859
860   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
861
862   if (transition == GST_STATE_CHANGE_READY_TO_NULL)
863     [self closeDevice];
864
865   return ret;
866 }
867
868 - (void)captureOutput:(AVCaptureOutput *)captureOutput
869 didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
870        fromConnection:(AVCaptureConnection *)aConnection
871 {
872   GstClockTime timestamp, duration;
873
874   [bufQueueLock lock];
875
876   if (stopRequest) {
877     [bufQueueLock unlock];
878     return;
879   }
880
881   [self getSampleBuffer:sampleBuffer timestamp:&timestamp duration:&duration];
882
883   if (timestamp == GST_CLOCK_TIME_NONE) {
884     [bufQueueLock unlockWithCondition:([bufQueue count] == 0) ? NO_BUFFERS : HAS_BUFFER_OR_STOP_REQUEST];
885     return;
886   }
887
888   if ([bufQueue count] == BUFFER_QUEUE_SIZE)
889     [bufQueue removeLastObject];
890
891   [bufQueue insertObject:@{@"sbuf": (id)sampleBuffer,
892                            @"timestamp": @(timestamp),
893                            @"duration": @(duration)}
894                  atIndex:0];
895
896   [bufQueueLock unlockWithCondition:HAS_BUFFER_OR_STOP_REQUEST];
897 }
898
899 - (GstFlowReturn)create:(GstBuffer **)buf
900 {
901   CMSampleBufferRef sbuf;
902   CVImageBufferRef image_buf;
903   CVPixelBufferRef pixel_buf;
904   size_t cur_width, cur_height;
905   GstClockTime timestamp, duration;
906
907   [bufQueueLock lockWhenCondition:HAS_BUFFER_OR_STOP_REQUEST];
908   if (stopRequest) {
909     [bufQueueLock unlock];
910     return GST_FLOW_FLUSHING;
911   }
912
913   NSDictionary *dic = (NSDictionary *) [bufQueue lastObject];
914   sbuf = (CMSampleBufferRef) dic[@"sbuf"];
915   timestamp = (GstClockTime) [dic[@"timestamp"] longLongValue];
916   duration = (GstClockTime) [dic[@"duration"] longLongValue];
917   CFRetain (sbuf);
918   [bufQueue removeLastObject];
919   [bufQueueLock unlockWithCondition:
920       ([bufQueue count] == 0) ? NO_BUFFERS : HAS_BUFFER_OR_STOP_REQUEST];
921
922   /* Check output frame size dimensions */
923   image_buf = CMSampleBufferGetImageBuffer (sbuf);
924   if (image_buf) {
925     pixel_buf = (CVPixelBufferRef) image_buf;
926     cur_width = CVPixelBufferGetWidth (pixel_buf);
927     cur_height = CVPixelBufferGetHeight (pixel_buf);
928
929     if (width != cur_width || height != cur_height) {
930       /* Set new caps according to current frame dimensions */
931       GST_WARNING ("Output frame size has changed %dx%d -> %dx%d, updating caps",
932           width, height, (int)cur_width, (int)cur_height);
933       width = cur_width;
934       height = cur_height;
935       gst_caps_set_simple (caps,
936         "width", G_TYPE_INT, width,
937         "height", G_TYPE_INT, height,
938         NULL);
939       gst_pad_push_event (GST_BASE_SINK_PAD (baseSrc), gst_event_new_caps (caps));
940     }
941   }
942
943   *buf = gst_core_media_buffer_new (sbuf, useVideoMeta, textureCache == NULL);
944   if (*buf == NULL) {
945     CFRelease (sbuf);
946     return GST_FLOW_ERROR;
947   }
948   CFRelease (sbuf);
949
950   if (textureCache != NULL) {
951     *buf = gst_video_texture_cache_get_gl_buffer (textureCache, *buf);
952     if (*buf == NULL)
953       return GST_FLOW_ERROR;
954   }
955
956   GST_BUFFER_OFFSET (*buf) = offset++;
957   GST_BUFFER_OFFSET_END (*buf) = GST_BUFFER_OFFSET (*buf) + 1;
958   GST_BUFFER_TIMESTAMP (*buf) = timestamp;
959   GST_BUFFER_DURATION (*buf) = duration;
960
961   if (doStats)
962     [self updateStatistics];
963
964   return GST_FLOW_OK;
965 }
966
967 static GstGLContext *
968 query_gl_context (GstPad *srcpad)
969 {
970   GstGLContext *gl_context = NULL;
971   GstContext *context = NULL;
972   GstQuery *query;
973
974   query = gst_query_new_context ("gst.gl.local_context");
975   if (gst_pad_peer_query (srcpad, query)) {
976     gst_query_parse_context (query, &context);
977     if (context) {
978       const GstStructure *s = gst_context_get_structure (context);
979       gst_structure_get (s, "context", GST_GL_TYPE_CONTEXT, &gl_context, NULL);
980     }
981   }
982   gst_query_unref (query);
983
984   return gl_context;
985 }
986
987 static gboolean
988 caps_filter_out_gl_memory (GstCapsFeatures * features, GstStructure * structure,
989     gpointer user_data)
990 {
991   return !gst_caps_features_contains (features,
992       GST_CAPS_FEATURE_MEMORY_GL_MEMORY);
993 }
994
995
996 - (GstCaps *)fixate:(GstCaps *)new_caps
997 {
998   GstGLContext *context;
999
1000   new_caps = gst_caps_make_writable (new_caps);
1001
1002   context = query_gl_context (GST_BASE_SRC_PAD (baseSrc));
1003   if (!context)
1004     gst_caps_filter_and_map_in_place (new_caps, caps_filter_out_gl_memory, NULL);
1005   else
1006     gst_object_unref (context);
1007
1008   return gst_caps_fixate (new_caps);
1009 }
1010
1011 - (void)getSampleBuffer:(CMSampleBufferRef)sbuf
1012               timestamp:(GstClockTime *)outTimestamp
1013                duration:(GstClockTime *)outDuration
1014 {
1015   CMSampleTimingInfo time_info;
1016   GstClockTime timestamp, avf_timestamp, duration, input_clock_now, input_clock_diff, running_time;
1017   CMItemCount num_timings;
1018   GstClock *clock;
1019   CMTime now;
1020
1021   timestamp = GST_CLOCK_TIME_NONE;
1022   duration = GST_CLOCK_TIME_NONE;
1023   if (CMSampleBufferGetOutputSampleTimingInfoArray(sbuf, 1, &time_info, &num_timings) == noErr) {
1024     avf_timestamp = gst_util_uint64_scale (GST_SECOND,
1025             time_info.presentationTimeStamp.value, time_info.presentationTimeStamp.timescale);
1026
1027     if (CMTIME_IS_VALID (time_info.duration) && time_info.duration.timescale != 0)
1028       duration = gst_util_uint64_scale (GST_SECOND,
1029           time_info.duration.value, time_info.duration.timescale);
1030
1031     now = CMClockGetTime(inputClock);
1032     input_clock_now = gst_util_uint64_scale (GST_SECOND,
1033         now.value, now.timescale);
1034     input_clock_diff = input_clock_now - avf_timestamp;
1035
1036     GST_OBJECT_LOCK (element);
1037     clock = GST_ELEMENT_CLOCK (element);
1038     if (clock) {
1039       running_time = gst_clock_get_time (clock) - element->base_time;
1040       /* We use presentationTimeStamp to determine how much time it took
1041        * between capturing and receiving the frame in our delegate
1042        * (e.g. how long it spent in AVF queues), then we subtract that time
1043        * from our running time to get the actual timestamp.
1044        */
1045       if (running_time >= input_clock_diff)
1046         timestamp = running_time - input_clock_diff;
1047       else
1048         timestamp = running_time;
1049
1050       GST_DEBUG_OBJECT (element, "AVF clock: %"GST_TIME_FORMAT ", AVF PTS: %"GST_TIME_FORMAT
1051           ", AVF clock diff: %"GST_TIME_FORMAT
1052           ", running time: %"GST_TIME_FORMAT ", out PTS: %"GST_TIME_FORMAT,
1053           GST_TIME_ARGS (input_clock_now), GST_TIME_ARGS (avf_timestamp),
1054           GST_TIME_ARGS (input_clock_diff),
1055           GST_TIME_ARGS (running_time), GST_TIME_ARGS (timestamp));
1056     } else {
1057       /* no clock, can't set timestamps */
1058       timestamp = GST_CLOCK_TIME_NONE;
1059     }
1060     GST_OBJECT_UNLOCK (element);
1061   }
1062
1063   *outTimestamp = timestamp;
1064   *outDuration = duration;
1065 }
1066
1067 - (void)updateStatistics
1068 {
1069   GstClock *clock;
1070
1071   GST_OBJECT_LOCK (element);
1072   clock = GST_ELEMENT_CLOCK (element);
1073   if (clock != NULL)
1074     gst_object_ref (clock);
1075   GST_OBJECT_UNLOCK (element);
1076
1077   if (clock != NULL) {
1078     GstClockTime now = gst_clock_get_time (clock);
1079     gst_object_unref (clock);
1080
1081     count++;
1082
1083     if (GST_CLOCK_TIME_IS_VALID (lastSampling)) {
1084       if (now - lastSampling >= GST_SECOND) {
1085         GST_OBJECT_LOCK (element);
1086         fps = count;
1087         GST_OBJECT_UNLOCK (element);
1088
1089         g_object_notify (G_OBJECT (element), "fps");
1090
1091         lastSampling = now;
1092         count = 0;
1093       }
1094     } else {
1095       lastSampling = now;
1096     }
1097   }
1098 }
1099
1100 @end
1101
1102 /*
1103  * Glue code
1104  */
1105
1106 enum
1107 {
1108   PROP_0,
1109   PROP_DEVICE_INDEX,
1110   PROP_DO_STATS,
1111   PROP_FPS,
1112 #if !HAVE_IOS
1113   PROP_CAPTURE_SCREEN,
1114   PROP_CAPTURE_SCREEN_CURSOR,
1115   PROP_CAPTURE_SCREEN_MOUSE_CLICKS,
1116 #endif
1117 };
1118
1119
1120 static void gst_avf_video_src_finalize (GObject * obj);
1121 static void gst_avf_video_src_get_property (GObject * object, guint prop_id,
1122     GValue * value, GParamSpec * pspec);
1123 static void gst_avf_video_src_set_property (GObject * object, guint prop_id,
1124     const GValue * value, GParamSpec * pspec);
1125 static GstStateChangeReturn gst_avf_video_src_change_state (
1126     GstElement * element, GstStateChange transition);
1127 static GstCaps * gst_avf_video_src_get_caps (GstBaseSrc * basesrc,
1128     GstCaps * filter);
1129 static gboolean gst_avf_video_src_set_caps (GstBaseSrc * basesrc,
1130     GstCaps * caps);
1131 static gboolean gst_avf_video_src_start (GstBaseSrc * basesrc);
1132 static gboolean gst_avf_video_src_stop (GstBaseSrc * basesrc);
1133 static gboolean gst_avf_video_src_query (GstBaseSrc * basesrc,
1134     GstQuery * query);
1135 static gboolean gst_avf_video_src_unlock (GstBaseSrc * basesrc);
1136 static gboolean gst_avf_video_src_unlock_stop (GstBaseSrc * basesrc);
1137 static GstFlowReturn gst_avf_video_src_create (GstPushSrc * pushsrc,
1138     GstBuffer ** buf);
1139 static gboolean gst_avf_video_src_negotiate (GstBaseSrc * basesrc);
1140 static GstCaps * gst_avf_video_src_fixate (GstBaseSrc * bsrc,
1141     GstCaps * caps);
1142
1143
1144 static void
1145 gst_avf_video_src_class_init (GstAVFVideoSrcClass * klass)
1146 {
1147   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1148   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
1149   GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
1150   GstPushSrcClass *gstpushsrc_class = GST_PUSH_SRC_CLASS (klass);
1151
1152   gobject_class->finalize = gst_avf_video_src_finalize;
1153   gobject_class->get_property = gst_avf_video_src_get_property;
1154   gobject_class->set_property = gst_avf_video_src_set_property;
1155
1156   gstelement_class->change_state = gst_avf_video_src_change_state;
1157
1158   gstbasesrc_class->get_caps = gst_avf_video_src_get_caps;
1159   gstbasesrc_class->set_caps = gst_avf_video_src_set_caps;
1160   gstbasesrc_class->start = gst_avf_video_src_start;
1161   gstbasesrc_class->stop = gst_avf_video_src_stop;
1162   gstbasesrc_class->query = gst_avf_video_src_query;
1163   gstbasesrc_class->unlock = gst_avf_video_src_unlock;
1164   gstbasesrc_class->unlock_stop = gst_avf_video_src_unlock_stop;
1165   gstbasesrc_class->fixate = gst_avf_video_src_fixate;
1166   gstbasesrc_class->negotiate = gst_avf_video_src_negotiate;
1167
1168   gstpushsrc_class->create = gst_avf_video_src_create;
1169
1170   gst_element_class_set_metadata (gstelement_class,
1171       "Video Source (AVFoundation)", "Source/Video",
1172       "Reads frames from an iOS AVFoundation device",
1173       "Ole André Vadla Ravnås <oleavr@soundrop.com>");
1174
1175   gst_element_class_add_pad_template (gstelement_class,
1176       gst_static_pad_template_get (&src_template));
1177
1178   g_object_class_install_property (gobject_class, PROP_DEVICE_INDEX,
1179       g_param_spec_int ("device-index", "Device Index",
1180           "The zero-based device index",
1181           -1, G_MAXINT, DEFAULT_DEVICE_INDEX,
1182           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1183   g_object_class_install_property (gobject_class, PROP_DO_STATS,
1184       g_param_spec_boolean ("do-stats", "Enable statistics",
1185           "Enable logging of statistics", DEFAULT_DO_STATS,
1186           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1187   g_object_class_install_property (gobject_class, PROP_FPS,
1188       g_param_spec_int ("fps", "Frames per second",
1189           "Last measured framerate, if statistics are enabled",
1190           -1, G_MAXINT, -1, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1191 #if !HAVE_IOS
1192   g_object_class_install_property (gobject_class, PROP_CAPTURE_SCREEN,
1193       g_param_spec_boolean ("capture-screen", "Enable screen capture",
1194           "Enable screen capture functionality", FALSE,
1195           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1196   g_object_class_install_property (gobject_class, PROP_CAPTURE_SCREEN_CURSOR,
1197       g_param_spec_boolean ("capture-screen-cursor", "Capture screen cursor",
1198           "Enable cursor capture while capturing screen", FALSE,
1199           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1200   g_object_class_install_property (gobject_class, PROP_CAPTURE_SCREEN_MOUSE_CLICKS,
1201       g_param_spec_boolean ("capture-screen-mouse-clicks", "Enable mouse clicks capture",
1202           "Enable mouse clicks capture while capturing screen", FALSE,
1203           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1204 #endif
1205
1206   GST_DEBUG_CATEGORY_INIT (gst_avf_video_src_debug, "avfvideosrc",
1207       0, "iOS AVFoundation video source");
1208 }
1209
1210 #define OBJC_CALLOUT_BEGIN() \
1211   NSAutoreleasePool *pool; \
1212   \
1213   pool = [[NSAutoreleasePool alloc] init]
1214 #define OBJC_CALLOUT_END() \
1215   [pool release]
1216
1217
1218 static void
1219 gst_avf_video_src_init (GstAVFVideoSrc * src)
1220 {
1221   OBJC_CALLOUT_BEGIN ();
1222   src->impl = [[GstAVFVideoSrcImpl alloc] initWithSrc:GST_PUSH_SRC (src)];
1223   OBJC_CALLOUT_END ();
1224 }
1225
1226 static void
1227 gst_avf_video_src_finalize (GObject * obj)
1228 {
1229   OBJC_CALLOUT_BEGIN ();
1230   [GST_AVF_VIDEO_SRC_IMPL (obj) release];
1231   OBJC_CALLOUT_END ();
1232
1233   G_OBJECT_CLASS (parent_class)->finalize (obj);
1234 }
1235
1236 static void
1237 gst_avf_video_src_get_property (GObject * object, guint prop_id, GValue * value,
1238     GParamSpec * pspec)
1239 {
1240   GstAVFVideoSrcImpl *impl = GST_AVF_VIDEO_SRC_IMPL (object);
1241
1242   switch (prop_id) {
1243 #if !HAVE_IOS
1244     case PROP_CAPTURE_SCREEN:
1245       g_value_set_boolean (value, impl.captureScreen);
1246       break;
1247     case PROP_CAPTURE_SCREEN_CURSOR:
1248       g_value_set_boolean (value, impl.captureScreenCursor);
1249       break;
1250     case PROP_CAPTURE_SCREEN_MOUSE_CLICKS:
1251       g_value_set_boolean (value, impl.captureScreenMouseClicks);
1252       break;
1253 #endif
1254     case PROP_DEVICE_INDEX:
1255       g_value_set_int (value, impl.deviceIndex);
1256       break;
1257     case PROP_DO_STATS:
1258       g_value_set_boolean (value, impl.doStats);
1259       break;
1260     case PROP_FPS:
1261       GST_OBJECT_LOCK (object);
1262       g_value_set_int (value, impl.fps);
1263       GST_OBJECT_UNLOCK (object);
1264       break;
1265     default:
1266       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1267       break;
1268   }
1269 }
1270
1271 static void
1272 gst_avf_video_src_set_property (GObject * object, guint prop_id,
1273     const GValue * value, GParamSpec * pspec)
1274 {
1275   GstAVFVideoSrcImpl *impl = GST_AVF_VIDEO_SRC_IMPL (object);
1276
1277   switch (prop_id) {
1278 #if !HAVE_IOS
1279     case PROP_CAPTURE_SCREEN:
1280       impl.captureScreen = g_value_get_boolean (value);
1281       break;
1282     case PROP_CAPTURE_SCREEN_CURSOR:
1283       impl.captureScreenCursor = g_value_get_boolean (value);
1284       break;
1285     case PROP_CAPTURE_SCREEN_MOUSE_CLICKS:
1286       impl.captureScreenMouseClicks = g_value_get_boolean (value);
1287       break;
1288 #endif
1289     case PROP_DEVICE_INDEX:
1290       impl.deviceIndex = g_value_get_int (value);
1291       break;
1292     case PROP_DO_STATS:
1293       impl.doStats = g_value_get_boolean (value);
1294       break;
1295     default:
1296       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1297       break;
1298   }
1299 }
1300
1301 static GstStateChangeReturn
1302 gst_avf_video_src_change_state (GstElement * element, GstStateChange transition)
1303 {
1304   GstStateChangeReturn ret;
1305
1306   OBJC_CALLOUT_BEGIN ();
1307   ret = [GST_AVF_VIDEO_SRC_IMPL (element) changeState: transition];
1308   OBJC_CALLOUT_END ();
1309
1310   return ret;
1311 }
1312
1313 static GstCaps *
1314 gst_avf_video_src_get_caps (GstBaseSrc * basesrc, GstCaps * filter)
1315 {
1316   GstCaps *ret;
1317
1318   OBJC_CALLOUT_BEGIN ();
1319   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) getCaps];
1320   OBJC_CALLOUT_END ();
1321
1322   return ret;
1323 }
1324
1325 static gboolean
1326 gst_avf_video_src_set_caps (GstBaseSrc * basesrc, GstCaps * caps)
1327 {
1328   gboolean ret;
1329
1330   OBJC_CALLOUT_BEGIN ();
1331   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) setCaps:caps];
1332   OBJC_CALLOUT_END ();
1333
1334   return ret;
1335 }
1336
1337 static gboolean
1338 gst_avf_video_src_start (GstBaseSrc * basesrc)
1339 {
1340   gboolean ret;
1341
1342   OBJC_CALLOUT_BEGIN ();
1343   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) start];
1344   OBJC_CALLOUT_END ();
1345
1346   return ret;
1347 }
1348
1349 static gboolean
1350 gst_avf_video_src_stop (GstBaseSrc * basesrc)
1351 {
1352   gboolean ret;
1353
1354   OBJC_CALLOUT_BEGIN ();
1355   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) stop];
1356   OBJC_CALLOUT_END ();
1357
1358   return ret;
1359 }
1360
1361 static gboolean
1362 gst_avf_video_src_query (GstBaseSrc * basesrc, GstQuery * query)
1363 {
1364   gboolean ret;
1365
1366   OBJC_CALLOUT_BEGIN ();
1367   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) query:query];
1368   OBJC_CALLOUT_END ();
1369
1370   return ret;
1371 }
1372
1373 static gboolean
1374 gst_avf_video_src_unlock (GstBaseSrc * basesrc)
1375 {
1376   gboolean ret;
1377
1378   OBJC_CALLOUT_BEGIN ();
1379   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) unlock];
1380   OBJC_CALLOUT_END ();
1381
1382   return ret;
1383 }
1384
1385 static gboolean
1386 gst_avf_video_src_unlock_stop (GstBaseSrc * basesrc)
1387 {
1388   gboolean ret;
1389
1390   OBJC_CALLOUT_BEGIN ();
1391   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) unlockStop];
1392   OBJC_CALLOUT_END ();
1393
1394   return ret;
1395 }
1396
1397 static GstFlowReturn
1398 gst_avf_video_src_create (GstPushSrc * pushsrc, GstBuffer ** buf)
1399 {
1400   GstFlowReturn ret;
1401
1402   OBJC_CALLOUT_BEGIN ();
1403   ret = [GST_AVF_VIDEO_SRC_IMPL (pushsrc) create: buf];
1404   OBJC_CALLOUT_END ();
1405
1406   return ret;
1407 }
1408
1409 static gboolean
1410 gst_avf_video_src_negotiate (GstBaseSrc * basesrc)
1411 {
1412   /* FIXME: We don't support reconfiguration yet */
1413   if (gst_pad_has_current_caps (GST_BASE_SRC_PAD (basesrc)))
1414     return TRUE;
1415
1416   return GST_BASE_SRC_CLASS (parent_class)->negotiate (basesrc);
1417 }
1418
1419
1420 static GstCaps *
1421 gst_avf_video_src_fixate (GstBaseSrc * bsrc, GstCaps * caps)
1422 {
1423   GstCaps *ret;
1424
1425   OBJC_CALLOUT_BEGIN ();
1426   ret = [GST_AVF_VIDEO_SRC_IMPL (bsrc) fixate:caps];
1427   OBJC_CALLOUT_END ();
1428
1429   return ret;
1430 }