avfvideosrc: fix build in iOS with isEqualTo doesn't exists
[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 #include <gst/video/video.h>
28 #include "coremediabuffer.h"
29
30 #define DEFAULT_DEVICE_INDEX  -1
31 #define DEFAULT_DO_STATS      FALSE
32
33 #define DEVICE_FPS_N          25
34 #define DEVICE_FPS_D          1
35
36 #define BUFFER_QUEUE_SIZE     2
37
38 GST_DEBUG_CATEGORY (gst_avf_video_src_debug);
39 #define GST_CAT_DEFAULT gst_avf_video_src_debug
40
41 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
42     GST_PAD_SRC,
43     GST_PAD_ALWAYS,
44     GST_STATIC_CAPS ("video/x-raw, "
45         "format = (string) { NV12, UYVY, YUY2 }, "
46         "framerate = " GST_VIDEO_FPS_RANGE ", "
47         "width = " GST_VIDEO_SIZE_RANGE ", "
48         "height = " GST_VIDEO_SIZE_RANGE "; "
49
50         "video/x-raw, "
51         "format = (string) { BGRA }, "
52         "bpp = (int) 32, "
53         "depth = (int) 32, "
54         "endianness = (int) BIG_ENDIAN, "
55         "red_mask = (int) 0x0000FF00, "
56         "green_mask = (int) 0x00FF0000, "
57         "blue_mask = (int) 0xFF000000, "
58         "alpha_mask = (int) 0x000000FF, "
59         "framerate = " GST_VIDEO_FPS_RANGE ", "
60         "width = " GST_VIDEO_SIZE_RANGE ", "
61         "height = " GST_VIDEO_SIZE_RANGE "; "
62 ));
63
64 typedef enum _QueueState {
65   NO_BUFFERS = 1,
66   HAS_BUFFER_OR_STOP_REQUEST,
67 } QueueState;
68
69 #define gst_avf_video_src_parent_class parent_class
70 G_DEFINE_TYPE (GstAVFVideoSrc, gst_avf_video_src, GST_TYPE_PUSH_SRC);
71
72 @interface GstAVFVideoSrcImpl : NSObject <AVCaptureVideoDataOutputSampleBufferDelegate> {
73   GstElement *element;
74   GstBaseSrc *baseSrc;
75   GstPushSrc *pushSrc;
76
77   gint deviceIndex;
78   BOOL doStats;
79 #if !HAVE_IOS
80   CGDirectDisplayID displayId;
81 #endif
82
83   AVCaptureSession *session;
84   AVCaptureInput *input;
85   AVCaptureVideoDataOutput *output;
86   AVCaptureDevice *device;
87
88   dispatch_queue_t mainQueue;
89   dispatch_queue_t workerQueue;
90   NSConditionLock *bufQueueLock;
91   NSMutableArray *bufQueue;
92   BOOL stopRequest;
93
94   GstCaps *caps;
95   GstVideoFormat format;
96   gint width, height;
97   GstClockTime duration;
98   guint64 offset;
99
100   GstClockTime lastSampling;
101   guint count;
102   gint fps;
103 }
104
105 - (id)init;
106 - (id)initWithSrc:(GstPushSrc *)src;
107 - (void)finalize;
108
109 @property int deviceIndex;
110 @property BOOL doStats;
111 @property int fps;
112 @property BOOL captureScreen;
113 @property BOOL captureScreenCursor;
114 @property BOOL captureScreenMouseClicks;
115
116 - (BOOL)openScreenInput;
117 - (BOOL)openDeviceInput;
118 - (BOOL)openDevice;
119 - (void)closeDevice;
120 - (GstVideoFormat)getGstVideoFormat:(NSNumber *)pixel_format;
121 - (BOOL)getDeviceCaps:(GstCaps *)result;
122 - (BOOL)setDeviceCaps:(GstVideoInfo *)info;
123 - (BOOL)getSessionPresetCaps:(GstCaps *)result;
124 - (BOOL)setSessionPresetCaps:(GstVideoInfo *)info;
125 - (GstCaps *)getCaps;
126 - (BOOL)setCaps:(GstCaps *)new_caps;
127 - (BOOL)start;
128 - (BOOL)stop;
129 - (BOOL)unlock;
130 - (BOOL)unlockStop;
131 - (BOOL)query:(GstQuery *)query;
132 - (GstStateChangeReturn)changeState:(GstStateChange)transition;
133 - (GstFlowReturn)create:(GstBuffer **)buf;
134 - (void)timestampBuffer:(GstBuffer *)buf;
135 - (void)updateStatistics;
136 - (void)captureOutput:(AVCaptureOutput *)captureOutput
137 didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
138        fromConnection:(AVCaptureConnection *)connection;
139
140 @end
141
142 @implementation GstAVFVideoSrcImpl
143
144 @synthesize deviceIndex, doStats, fps, captureScreen,
145             captureScreenCursor, captureScreenMouseClicks;
146
147 - (id)init
148 {
149   return [self initWithSrc:NULL];
150 }
151
152 - (id)initWithSrc:(GstPushSrc *)src
153 {
154   if ((self = [super init])) {
155     element = GST_ELEMENT_CAST (src);
156     baseSrc = GST_BASE_SRC_CAST (src);
157     pushSrc = src;
158
159     deviceIndex = DEFAULT_DEVICE_INDEX;
160     captureScreen = NO;
161     captureScreenCursor = NO;
162     captureScreenMouseClicks = NO;
163 #if !HAVE_IOS
164     displayId = kCGDirectMainDisplay;
165 #endif
166
167     mainQueue =
168         dispatch_queue_create ("org.freedesktop.gstreamer.avfvideosrc.main", NULL);
169     workerQueue =
170         dispatch_queue_create ("org.freedesktop.gstreamer.avfvideosrc.output", NULL);
171
172     gst_base_src_set_live (baseSrc, TRUE);
173     gst_base_src_set_format (baseSrc, GST_FORMAT_TIME);
174   }
175
176   return self;
177 }
178
179 - (void)finalize
180 {
181   dispatch_release (mainQueue);
182   mainQueue = NULL;
183   dispatch_release (workerQueue);
184   workerQueue = NULL;
185
186   [super finalize];
187 }
188
189 - (BOOL)openDeviceInput
190 {
191   NSString *mediaType = AVMediaTypeVideo;
192   NSError *err;
193
194   if (deviceIndex == -1) {
195     device = [AVCaptureDevice defaultDeviceWithMediaType:mediaType];
196     if (device == nil) {
197       GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
198                           ("No video capture devices found"), (NULL));
199       return NO;
200     }
201   } else {
202     NSArray *devices = [AVCaptureDevice devicesWithMediaType:mediaType];
203     if (deviceIndex >= [devices count]) {
204       GST_ELEMENT_ERROR (element, RESOURCE, NOT_FOUND,
205                           ("Invalid video capture device index"), (NULL));
206       return NO;
207     }
208     device = [devices objectAtIndex:deviceIndex];
209   }
210   g_assert (device != nil);
211   [device retain];
212
213   GST_INFO ("Opening '%s'", [[device localizedName] UTF8String]);
214
215   input = [AVCaptureDeviceInput deviceInputWithDevice:device
216                                                 error:&err];
217   if (input == nil) {
218     GST_ELEMENT_ERROR (element, RESOURCE, BUSY,
219         ("Failed to open device: %s",
220         [[err localizedDescription] UTF8String]),
221         (NULL));
222     [device release];
223     device = nil;
224     return NO;
225   }
226   [input retain];
227   return YES;
228 }
229
230 - (BOOL)openScreenInput
231 {
232 #if HAVE_IOS
233   return NO;
234 #else
235   GST_DEBUG_OBJECT (element, "Opening screen input");
236
237   AVCaptureScreenInput *screenInput =
238       [[AVCaptureScreenInput alloc] initWithDisplayID:displayId];
239
240
241   @try {
242     [screenInput setValue:[NSNumber numberWithBool:captureScreenCursor]
243                  forKey:@"capturesCursor"];
244
245   } @catch (NSException *exception) {
246     if (![[exception name] isEqualToString:NSUndefinedKeyException]) {
247       GST_WARNING ("An unexpected error occured: %s",
248                    [[exception reason] UTF8String]);
249     }
250     GST_WARNING ("Capturing cursor is only supported in OS X >= 10.8");
251   }
252   screenInput.capturesMouseClicks = captureScreenMouseClicks;
253   input = screenInput;
254   [input retain];
255   return YES;
256 #endif
257 }
258
259 - (BOOL)openDevice
260 {
261   BOOL success = NO, *successPtr = &success;
262
263   GST_DEBUG_OBJECT (element, "Opening device");
264
265   dispatch_sync (mainQueue, ^{
266     BOOL ret;
267
268     if (captureScreen)
269       ret = [self openScreenInput];
270     else
271       ret = [self openDeviceInput];
272
273     if (!ret)
274       return;
275
276     output = [[AVCaptureVideoDataOutput alloc] init];
277     [output setSampleBufferDelegate:self
278                               queue:workerQueue];
279     output.alwaysDiscardsLateVideoFrames = YES;
280     output.videoSettings = nil; /* device native format */
281
282     session = [[AVCaptureSession alloc] init];
283     [session addInput:input];
284     [session addOutput:output];
285
286     *successPtr = YES;
287   });
288
289   GST_DEBUG_OBJECT (element, "Opening device %s", success ? "succeed" : "failed");
290
291   return success;
292 }
293
294 - (void)closeDevice
295 {
296   GST_DEBUG_OBJECT (element, "Closing device");
297
298   dispatch_sync (mainQueue, ^{
299     g_assert (![session isRunning]);
300
301     [session removeInput:input];
302     [session removeOutput:output];
303
304     [session release];
305     session = nil;
306
307     [input release];
308     input = nil;
309
310     [output release];
311     output = nil;
312
313     if (!captureScreen) {
314       [device release];
315       device = nil;
316     }
317
318     if (caps)
319       gst_caps_unref (caps);
320   });
321 }
322
323 #define GST_AVF_CAPS_NEW(format, w, h, fps_n, fps_d)                  \
324     (gst_caps_new_simple ("video/x-raw",                              \
325         "width", G_TYPE_INT, w,                                       \
326         "height", G_TYPE_INT, h,                                      \
327         "format", G_TYPE_STRING, gst_video_format_to_string (format), \
328         "framerate", GST_TYPE_FRACTION, (fps_n), (fps_d),             \
329         NULL))
330
331 - (GstVideoFormat)getGstVideoFormat:(NSNumber *)pixel_format
332 {
333   GstVideoFormat gst_format = GST_VIDEO_FORMAT_UNKNOWN;
334
335   switch ([pixel_format integerValue]) {
336   case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: /* 420v */
337     gst_format = GST_VIDEO_FORMAT_NV12;
338     break;
339   case kCVPixelFormatType_422YpCbCr8: /* 2vuy */
340     gst_format = GST_VIDEO_FORMAT_UYVY;
341     break;
342   case kCVPixelFormatType_32BGRA: /* BGRA */
343     gst_format = GST_VIDEO_FORMAT_BGRA;
344     break;
345   case kCVPixelFormatType_422YpCbCr8_yuvs: /* yuvs */
346     gst_format = GST_VIDEO_FORMAT_YUY2;
347     break;
348   default:
349     GST_LOG_OBJECT (element, "Pixel format %s is not handled by avfvideosrc",
350         [[pixel_format stringValue] UTF8String]);
351     break;
352   }
353
354   return gst_format;
355 }
356
357 - (BOOL)getDeviceCaps:(GstCaps *)result
358 {
359   NSArray *formats = [device valueForKey:@"formats"];
360   NSArray *pixel_formats = output.availableVideoCVPixelFormatTypes;
361
362   GST_DEBUG_OBJECT (element, "Getting device caps");
363
364   /* Do not use AVCaptureDeviceFormat or AVFrameRateRange only
365    * available in iOS >= 7.0. We use a dynamic approach with key-value
366    * coding or performSelector */
367   for (NSObject *f in [formats reverseObjectEnumerator]) {
368     CMFormatDescriptionRef formatDescription;
369     CMVideoDimensions dimensions;
370
371     /* formatDescription can't be retrieved with valueForKey so use a selector here */
372     formatDescription = (CMFormatDescriptionRef) [f performSelector:@selector(formatDescription)];
373     dimensions = CMVideoFormatDescriptionGetDimensions(formatDescription);
374     for (NSObject *rate in [f valueForKey:@"videoSupportedFrameRateRanges"]) {
375       int fps_n, fps_d;
376       gdouble max_fps;
377
378       [[rate valueForKey:@"maxFrameRate"] getValue:&max_fps];
379       gst_util_double_to_fraction (max_fps, &fps_n, &fps_d);
380
381       for (NSNumber *pixel_format in pixel_formats) {
382         GstVideoFormat gst_format = [self getGstVideoFormat:pixel_format];
383         if (gst_format != GST_VIDEO_FORMAT_UNKNOWN)
384           gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, dimensions.width, dimensions.height, fps_n, fps_d));
385       }
386     }
387   }
388   GST_LOG_OBJECT (element, "Device returned the following caps %" GST_PTR_FORMAT, result);
389   return YES;
390 }
391
392 - (BOOL)setDeviceCaps:(GstVideoInfo *)info
393 {
394   double framerate;
395   gboolean found_format = FALSE, found_framerate = FALSE;
396   NSArray *formats = [device valueForKey:@"formats"];
397   gst_util_fraction_to_double (info->fps_n, info->fps_d, &framerate);
398
399   GST_DEBUG_OBJECT (element, "Setting device caps");
400
401   if ([device lockForConfiguration:NULL] == YES) {
402     for (NSObject *f in formats) {
403       CMFormatDescriptionRef formatDescription;
404       CMVideoDimensions dimensions;
405
406       formatDescription = (CMFormatDescriptionRef) [f performSelector:@selector(formatDescription)];
407       dimensions = CMVideoFormatDescriptionGetDimensions(formatDescription);
408       if (dimensions.width == info->width && dimensions.height == info->height) {
409         found_format = TRUE;
410         [device setValue:f forKey:@"activeFormat"];
411         for (NSObject *rate in [f valueForKey:@"videoSupportedFrameRateRanges"]) {
412           gdouble max_frame_rate;
413
414           [[rate valueForKey:@"maxFrameRate"] getValue:&max_frame_rate];
415           if (abs (framerate - max_frame_rate) < 0.00001) {
416             NSValue *min_frame_duration, *max_frame_duration;
417
418             found_framerate = TRUE;
419             min_frame_duration = [rate valueForKey:@"minFrameDuration"];
420             max_frame_duration = [rate valueForKey:@"maxFrameDuration"];
421             [device setValue:min_frame_duration forKey:@"activeVideoMinFrameDuration"];
422             @try {
423               /* Only available on OSX >= 10.8 and iOS >= 7.0 */
424               [device setValue:max_frame_duration forKey:@"activeVideoMaxFrameDuration"];
425             } @catch (NSException *exception) {
426               if (![[exception name] isEqualToString:NSUndefinedKeyException]) {
427                 GST_WARNING ("An unexcepted error occured: %s",
428                               [exception.reason UTF8String]);
429               }
430             }
431             break;
432           }
433         }
434       }
435     }
436     if (!found_format) {
437       GST_WARNING ("Unsupported capture dimensions %dx%d", info->width, info->height);
438       return NO;
439     }
440     if (!found_framerate) {
441       GST_WARNING ("Unsupported capture framerate %d/%d", info->fps_n, info->fps_d);
442       return NO;
443     }
444   } else {
445     GST_WARNING ("Couldn't lock device for configuration");
446     return NO;
447   }
448   return YES;
449 }
450
451 - (BOOL)getSessionPresetCaps:(GstCaps *)result
452 {
453   NSArray *pixel_formats = output.availableVideoCVPixelFormatTypes;
454   for (NSNumber *pixel_format in pixel_formats) {
455     GstVideoFormat gst_format = [self getGstVideoFormat:pixel_format];
456     if (gst_format == GST_VIDEO_FORMAT_UNKNOWN)
457       continue;
458
459 #if HAVE_IOS
460     if ([session canSetSessionPreset:AVCaptureSessionPreset1920x1080])
461       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 1920, 1080, DEVICE_FPS_N, DEVICE_FPS_D));
462 #endif
463     if ([session canSetSessionPreset:AVCaptureSessionPreset1280x720])
464       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 1280, 720, DEVICE_FPS_N, DEVICE_FPS_D));
465     if ([session canSetSessionPreset:AVCaptureSessionPreset640x480])
466       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 640, 480, DEVICE_FPS_N, DEVICE_FPS_D));
467     if ([session canSetSessionPreset:AVCaptureSessionPresetMedium])
468       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 480, 360, DEVICE_FPS_N, DEVICE_FPS_D));
469     if ([session canSetSessionPreset:AVCaptureSessionPreset352x288])
470       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 352, 288, DEVICE_FPS_N, DEVICE_FPS_D));
471     if ([session canSetSessionPreset:AVCaptureSessionPresetLow])
472       gst_caps_append (result, GST_AVF_CAPS_NEW (gst_format, 192, 144, DEVICE_FPS_N, DEVICE_FPS_D));
473   }
474
475   GST_LOG_OBJECT (element, "Session presets returned the following caps %" GST_PTR_FORMAT, result);
476
477   return YES;
478 }
479
480 - (BOOL)setSessionPresetCaps:(GstVideoInfo *)info;
481 {
482   GST_DEBUG_OBJECT (element, "Setting session presset caps");
483
484   if ([device lockForConfiguration:NULL] != YES) {
485     GST_WARNING ("Couldn't lock device for configuration");
486     return NO;
487   }
488
489   switch (info->width) {
490   case 192:
491     session.sessionPreset = AVCaptureSessionPresetLow;
492     break;
493   case 352:
494     session.sessionPreset = AVCaptureSessionPreset352x288;
495     break;
496   case 480:
497     session.sessionPreset = AVCaptureSessionPresetMedium;
498     break;
499   case 640:
500     session.sessionPreset = AVCaptureSessionPreset640x480;
501     break;
502   case 1280:
503     session.sessionPreset = AVCaptureSessionPreset1280x720;
504     break;
505 #if HAVE_IOS
506   case 1920:
507     session.sessionPreset = AVCaptureSessionPreset1920x1080;
508     break;
509 #endif
510   default:
511     GST_WARNING ("Unsupported capture dimensions %dx%d", info->width, info->height);
512     return NO;
513   }
514   return YES;
515 }
516
517 - (GstCaps *)getCaps
518 {
519   GstCaps *result;
520   NSArray *pixel_formats;
521
522   if (session == nil)
523     return NULL; /* BaseSrc will return template caps */
524
525   result = gst_caps_new_empty ();
526   pixel_formats = output.availableVideoCVPixelFormatTypes;
527
528   if (captureScreen) {
529 #if !HAVE_IOS
530     CGRect rect = CGDisplayBounds (displayId);
531     for (NSNumber *pixel_format in pixel_formats) {
532       GstVideoFormat gst_format = [self getGstVideoFormat:pixel_format];
533       if (gst_format != GST_VIDEO_FORMAT_UNKNOWN)
534         gst_caps_append (result, gst_caps_new_simple ("video/x-raw",
535             "width", G_TYPE_INT, (int)rect.size.width,
536             "height", G_TYPE_INT, (int)rect.size.height,
537             "format", G_TYPE_STRING, gst_video_format_to_string (gst_format),
538             NULL));
539     }
540 #else
541     GST_WARNING ("Screen capture is not supported by iOS");
542 #endif
543     return result;
544   }
545
546   @try {
547
548     [self getDeviceCaps:result];
549
550   } @catch (NSException *exception) {
551
552     if (![[exception name] isEqualToString:NSUndefinedKeyException]) {
553       GST_WARNING ("An unexcepted error occured: %s", [exception.reason UTF8String]);
554       return result;
555     }
556
557     /* Fallback on session presets API for iOS < 7.0 */
558     [self getSessionPresetCaps:result];
559   }
560
561   return result;
562 }
563
564 - (BOOL)setCaps:(GstCaps *)new_caps
565 {
566   GstVideoInfo info;
567   BOOL success = YES, *successPtr = &success;
568
569   gst_video_info_init (&info);
570   gst_video_info_from_caps (&info, new_caps);
571
572   width = info.width;
573   height = info.height;
574   format = info.finfo->format;
575
576   dispatch_sync (mainQueue, ^{
577     int newformat;
578
579     g_assert (![session isRunning]);
580
581     if (captureScreen) {
582 #if !HAVE_IOS
583       AVCaptureScreenInput *screenInput = (AVCaptureScreenInput *)input;
584       screenInput.minFrameDuration = CMTimeMake(info.fps_d, info.fps_n);
585 #else
586       GST_WARNING ("Screen capture is not supported by iOS");
587       *successPtr = NO;
588       return;
589 #endif
590     } else {
591       @try {
592
593         /* formats and activeFormat keys are only available on OSX >= 10.7 and iOS >= 7.0 */
594         *successPtr = [self setDeviceCaps:(GstVideoInfo *)&info];
595         if (*successPtr != YES)
596           return;
597
598       } @catch (NSException *exception) {
599
600         if (![[exception name] isEqualToString:NSUndefinedKeyException]) {
601           GST_WARNING ("An unexcepted error occured: %s", [exception.reason UTF8String]);
602           *successPtr = NO;
603           return;
604         }
605
606         /* Fallback on session presets API for iOS < 7.0 */
607         *successPtr = [self setSessionPresetCaps:(GstVideoInfo *)&info];
608         if (*successPtr != YES)
609           return;
610       }
611     }
612
613     switch (format) {
614       case GST_VIDEO_FORMAT_NV12:
615         newformat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
616         break;
617       case GST_VIDEO_FORMAT_UYVY:
618          newformat = kCVPixelFormatType_422YpCbCr8;
619         break;
620       case GST_VIDEO_FORMAT_YUY2:
621          newformat = kCVPixelFormatType_422YpCbCr8_yuvs;
622         break;
623       case GST_VIDEO_FORMAT_BGRA:
624          newformat = kCVPixelFormatType_32BGRA;
625         break;
626       default:
627         *successPtr = NO;
628         GST_WARNING ("Unsupported output format %s",
629             gst_video_format_to_string (format));
630         return;
631     }
632
633     GST_DEBUG_OBJECT(element,
634        "Width: %d Height: %d Format: %" GST_FOURCC_FORMAT,
635        width, height,
636        GST_FOURCC_ARGS (gst_video_format_to_fourcc (format)));
637
638     output.videoSettings = [NSDictionary
639         dictionaryWithObject:[NSNumber numberWithInt:newformat]
640         forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey];
641
642     caps = gst_caps_copy (new_caps);
643     [session startRunning];
644
645     /* Unlock device configuration only after session is started so the session
646      * won't reset the capture formats */
647     [device unlockForConfiguration];
648   });
649
650   return success;
651 }
652
653 - (BOOL)start
654 {
655   bufQueueLock = [[NSConditionLock alloc] initWithCondition:NO_BUFFERS];
656   bufQueue = [[NSMutableArray alloc] initWithCapacity:BUFFER_QUEUE_SIZE];
657   stopRequest = NO;
658
659   duration = gst_util_uint64_scale (GST_SECOND, DEVICE_FPS_D, DEVICE_FPS_N);
660   offset = 0;
661
662   lastSampling = GST_CLOCK_TIME_NONE;
663   count = 0;
664   fps = -1;
665
666   return YES;
667 }
668
669 - (BOOL)stop
670 {
671   dispatch_sync (mainQueue, ^{ [session stopRunning]; });
672   dispatch_sync (workerQueue, ^{});
673
674   [bufQueueLock release];
675   bufQueueLock = nil;
676   [bufQueue release];
677   bufQueue = nil;
678
679   return YES;
680 }
681
682 - (BOOL)query:(GstQuery *)query
683 {
684   BOOL result = NO;
685
686   if (GST_QUERY_TYPE (query) == GST_QUERY_LATENCY) {
687     if (device != nil) {
688       GstClockTime min_latency, max_latency;
689
690       min_latency = max_latency = duration; /* for now */
691       result = YES;
692
693       GST_DEBUG_OBJECT (element, "reporting latency of min %" GST_TIME_FORMAT
694           " max %" GST_TIME_FORMAT,
695           GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency));
696       gst_query_set_latency (query, TRUE, min_latency, max_latency);
697     }
698   } else {
699     result = GST_BASE_SRC_CLASS (parent_class)->query (baseSrc, query);
700   }
701
702   return result;
703 }
704
705 - (BOOL)unlock
706 {
707   [bufQueueLock lock];
708   stopRequest = YES;
709   [bufQueueLock unlockWithCondition:HAS_BUFFER_OR_STOP_REQUEST];
710
711   return YES;
712 }
713
714 - (BOOL)unlockStop
715 {
716   [bufQueueLock lock];
717   stopRequest = NO;
718   [bufQueueLock unlock];
719
720   return YES;
721 }
722
723 - (GstStateChangeReturn)changeState:(GstStateChange)transition
724 {
725   GstStateChangeReturn ret;
726
727   if (transition == GST_STATE_CHANGE_NULL_TO_READY) {
728     if (![self openDevice])
729       return GST_STATE_CHANGE_FAILURE;
730   }
731
732   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
733
734   if (transition == GST_STATE_CHANGE_READY_TO_NULL)
735     [self closeDevice];
736
737   return ret;
738 }
739
740 - (void)captureOutput:(AVCaptureOutput *)captureOutput
741 didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
742        fromConnection:(AVCaptureConnection *)connection
743 {
744   [bufQueueLock lock];
745
746   if (stopRequest) {
747     [bufQueueLock unlock];
748     return;
749   }
750
751   if ([bufQueue count] == BUFFER_QUEUE_SIZE)
752     [bufQueue removeLastObject];
753
754   [bufQueue insertObject:(id)sampleBuffer
755                  atIndex:0];
756
757   [bufQueueLock unlockWithCondition:HAS_BUFFER_OR_STOP_REQUEST];
758 }
759
760 - (GstFlowReturn)create:(GstBuffer **)buf
761 {
762   CMSampleBufferRef sbuf;
763   CVImageBufferRef image_buf;
764   CVPixelBufferRef pixel_buf;
765   size_t cur_width, cur_height;
766
767   [bufQueueLock lockWhenCondition:HAS_BUFFER_OR_STOP_REQUEST];
768   if (stopRequest) {
769     [bufQueueLock unlock];
770     return GST_FLOW_FLUSHING;
771   }
772
773   sbuf = (CMSampleBufferRef) [bufQueue lastObject];
774   CFRetain (sbuf);
775   [bufQueue removeLastObject];
776   [bufQueueLock unlockWithCondition:
777       ([bufQueue count] == 0) ? NO_BUFFERS : HAS_BUFFER_OR_STOP_REQUEST];
778
779   /* Check output frame size dimensions */
780   image_buf = CMSampleBufferGetImageBuffer (sbuf);
781   if (image_buf) {
782     pixel_buf = (CVPixelBufferRef) image_buf;
783     cur_width = CVPixelBufferGetWidth (pixel_buf);
784     cur_height = CVPixelBufferGetHeight (pixel_buf);
785
786     if (width != cur_width || height != cur_height) {
787       /* Set new caps according to current frame dimensions */
788       GST_WARNING ("Output frame size has changed %dx%d -> %dx%d, updating caps",
789           width, height, (int)cur_width, (int)cur_height);
790       width = cur_width;
791       height = cur_height;
792       gst_caps_set_simple (caps,
793         "width", G_TYPE_INT, width,
794         "height", G_TYPE_INT, height,
795         NULL);
796       gst_pad_push_event (GST_BASE_SINK_PAD (baseSrc), gst_event_new_caps (caps));
797     }
798   }
799
800   *buf = gst_core_media_buffer_new (sbuf);
801   CFRelease (sbuf);
802
803   [self timestampBuffer:*buf];
804
805   if (doStats)
806     [self updateStatistics];
807
808   return GST_FLOW_OK;
809 }
810
811 - (void)timestampBuffer:(GstBuffer *)buf
812 {
813   GstClock *clock;
814   GstClockTime timestamp;
815
816   GST_OBJECT_LOCK (element);
817   clock = GST_ELEMENT_CLOCK (element);
818   if (clock != NULL) {
819     gst_object_ref (clock);
820     timestamp = element->base_time;
821   } else {
822     timestamp = GST_CLOCK_TIME_NONE;
823   }
824   GST_OBJECT_UNLOCK (element);
825
826   if (clock != NULL) {
827     timestamp = gst_clock_get_time (clock) - timestamp;
828     if (timestamp > duration)
829       timestamp -= duration;
830     else
831       timestamp = 0;
832
833     gst_object_unref (clock);
834     clock = NULL;
835   }
836
837   GST_BUFFER_OFFSET (buf) = offset++;
838   GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET (buf) + 1;
839   GST_BUFFER_TIMESTAMP (buf) = timestamp;
840   GST_BUFFER_DURATION (buf) = duration;
841 }
842
843 - (void)updateStatistics
844 {
845   GstClock *clock;
846
847   GST_OBJECT_LOCK (element);
848   clock = GST_ELEMENT_CLOCK (element);
849   if (clock != NULL)
850     gst_object_ref (clock);
851   GST_OBJECT_UNLOCK (element);
852
853   if (clock != NULL) {
854     GstClockTime now = gst_clock_get_time (clock);
855     gst_object_unref (clock);
856
857     count++;
858
859     if (GST_CLOCK_TIME_IS_VALID (lastSampling)) {
860       if (now - lastSampling >= GST_SECOND) {
861         GST_OBJECT_LOCK (element);
862         fps = count;
863         GST_OBJECT_UNLOCK (element);
864
865         g_object_notify (G_OBJECT (element), "fps");
866
867         lastSampling = now;
868         count = 0;
869       }
870     } else {
871       lastSampling = now;
872     }
873   }
874 }
875
876 @end
877
878 /*
879  * Glue code
880  */
881
882 enum
883 {
884   PROP_0,
885   PROP_DEVICE_INDEX,
886   PROP_DO_STATS,
887   PROP_FPS,
888 #if !HAVE_IOS
889   PROP_CAPTURE_SCREEN,
890   PROP_CAPTURE_SCREEN_CURSOR,
891   PROP_CAPTURE_SCREEN_MOUSE_CLICKS,
892 #endif
893 };
894
895
896 static void gst_avf_video_src_finalize (GObject * obj);
897 static void gst_avf_video_src_get_property (GObject * object, guint prop_id,
898     GValue * value, GParamSpec * pspec);
899 static void gst_avf_video_src_set_property (GObject * object, guint prop_id,
900     const GValue * value, GParamSpec * pspec);
901 static GstStateChangeReturn gst_avf_video_src_change_state (
902     GstElement * element, GstStateChange transition);
903 static GstCaps * gst_avf_video_src_get_caps (GstBaseSrc * basesrc,
904     GstCaps * filter);
905 static gboolean gst_avf_video_src_set_caps (GstBaseSrc * basesrc,
906     GstCaps * caps);
907 static gboolean gst_avf_video_src_start (GstBaseSrc * basesrc);
908 static gboolean gst_avf_video_src_stop (GstBaseSrc * basesrc);
909 static gboolean gst_avf_video_src_query (GstBaseSrc * basesrc,
910     GstQuery * query);
911 static gboolean gst_avf_video_src_unlock (GstBaseSrc * basesrc);
912 static gboolean gst_avf_video_src_unlock_stop (GstBaseSrc * basesrc);
913 static GstFlowReturn gst_avf_video_src_create (GstPushSrc * pushsrc,
914     GstBuffer ** buf);
915
916
917 static void
918 gst_avf_video_src_class_init (GstAVFVideoSrcClass * klass)
919 {
920   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
921   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
922   GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
923   GstPushSrcClass *gstpushsrc_class = GST_PUSH_SRC_CLASS (klass);
924
925   gobject_class->finalize = gst_avf_video_src_finalize;
926   gobject_class->get_property = gst_avf_video_src_get_property;
927   gobject_class->set_property = gst_avf_video_src_set_property;
928
929   gstelement_class->change_state = gst_avf_video_src_change_state;
930
931   gstbasesrc_class->get_caps = gst_avf_video_src_get_caps;
932   gstbasesrc_class->set_caps = gst_avf_video_src_set_caps;
933   gstbasesrc_class->start = gst_avf_video_src_start;
934   gstbasesrc_class->stop = gst_avf_video_src_stop;
935   gstbasesrc_class->query = gst_avf_video_src_query;
936   gstbasesrc_class->unlock = gst_avf_video_src_unlock;
937   gstbasesrc_class->unlock_stop = gst_avf_video_src_unlock_stop;
938
939   gstpushsrc_class->create = gst_avf_video_src_create;
940
941   gst_element_class_set_metadata (gstelement_class,
942       "Video Source (AVFoundation)", "Source/Video",
943       "Reads frames from an iOS AVFoundation device",
944       "Ole André Vadla Ravnås <oleavr@soundrop.com>");
945
946   gst_element_class_add_pad_template (gstelement_class,
947       gst_static_pad_template_get (&src_template));
948
949   g_object_class_install_property (gobject_class, PROP_DEVICE_INDEX,
950       g_param_spec_int ("device-index", "Device Index",
951           "The zero-based device index",
952           -1, G_MAXINT, DEFAULT_DEVICE_INDEX,
953           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
954   g_object_class_install_property (gobject_class, PROP_DO_STATS,
955       g_param_spec_boolean ("do-stats", "Enable statistics",
956           "Enable logging of statistics", DEFAULT_DO_STATS,
957           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
958   g_object_class_install_property (gobject_class, PROP_FPS,
959       g_param_spec_int ("fps", "Frames per second",
960           "Last measured framerate, if statistics are enabled",
961           -1, G_MAXINT, -1, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
962 #if !HAVE_IOS
963   g_object_class_install_property (gobject_class, PROP_CAPTURE_SCREEN,
964       g_param_spec_boolean ("capture-screen", "Enable screen capture",
965           "Enable screen capture functionality", FALSE,
966           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
967   g_object_class_install_property (gobject_class, PROP_CAPTURE_SCREEN_CURSOR,
968       g_param_spec_boolean ("capture-screen-cursor", "Capture screen cursor",
969           "Enable cursor capture while capturing screen", FALSE,
970           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
971   g_object_class_install_property (gobject_class, PROP_CAPTURE_SCREEN_MOUSE_CLICKS,
972       g_param_spec_boolean ("capture-screen-mouse-clicks", "Enable mouse clicks capture",
973           "Enable mouse clicks capture while capturing screen", FALSE,
974           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
975 #endif
976
977   GST_DEBUG_CATEGORY_INIT (gst_avf_video_src_debug, "avfvideosrc",
978       0, "iOS AVFoundation video source");
979 }
980
981 #define OBJC_CALLOUT_BEGIN() \
982   NSAutoreleasePool *pool; \
983   \
984   pool = [[NSAutoreleasePool alloc] init]
985 #define OBJC_CALLOUT_END() \
986   [pool release]
987
988
989 static void
990 gst_avf_video_src_init (GstAVFVideoSrc * src)
991 {
992   OBJC_CALLOUT_BEGIN ();
993   src->impl = [[GstAVFVideoSrcImpl alloc] initWithSrc:GST_PUSH_SRC (src)];
994   OBJC_CALLOUT_END ();
995 }
996
997 static void
998 gst_avf_video_src_finalize (GObject * obj)
999 {
1000   OBJC_CALLOUT_BEGIN ();
1001   [GST_AVF_VIDEO_SRC_IMPL (obj) release];
1002   OBJC_CALLOUT_END ();
1003
1004   G_OBJECT_CLASS (parent_class)->finalize (obj);
1005 }
1006
1007 static void
1008 gst_avf_video_src_get_property (GObject * object, guint prop_id, GValue * value,
1009     GParamSpec * pspec)
1010 {
1011   GstAVFVideoSrcImpl *impl = GST_AVF_VIDEO_SRC_IMPL (object);
1012
1013   switch (prop_id) {
1014 #if !HAVE_IOS
1015     case PROP_CAPTURE_SCREEN:
1016       g_value_set_boolean (value, impl.captureScreen);
1017       break;
1018     case PROP_CAPTURE_SCREEN_CURSOR:
1019       g_value_set_boolean (value, impl.captureScreenCursor);
1020       break;
1021     case PROP_CAPTURE_SCREEN_MOUSE_CLICKS:
1022       g_value_set_boolean (value, impl.captureScreenMouseClicks);
1023       break;
1024 #endif
1025     case PROP_DEVICE_INDEX:
1026       g_value_set_int (value, impl.deviceIndex);
1027       break;
1028     case PROP_DO_STATS:
1029       g_value_set_boolean (value, impl.doStats);
1030       break;
1031     case PROP_FPS:
1032       GST_OBJECT_LOCK (object);
1033       g_value_set_int (value, impl.fps);
1034       GST_OBJECT_UNLOCK (object);
1035       break;
1036     default:
1037       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1038       break;
1039   }
1040 }
1041
1042 static void
1043 gst_avf_video_src_set_property (GObject * object, guint prop_id,
1044     const GValue * value, GParamSpec * pspec)
1045 {
1046   GstAVFVideoSrcImpl *impl = GST_AVF_VIDEO_SRC_IMPL (object);
1047
1048   switch (prop_id) {
1049 #if !HAVE_IOS
1050     case PROP_CAPTURE_SCREEN:
1051       impl.captureScreen = g_value_get_boolean (value);
1052       break;
1053     case PROP_CAPTURE_SCREEN_CURSOR:
1054       impl.captureScreenCursor = g_value_get_boolean (value);
1055       break;
1056     case PROP_CAPTURE_SCREEN_MOUSE_CLICKS:
1057       impl.captureScreenMouseClicks = g_value_get_boolean (value);
1058       break;
1059 #endif
1060     case PROP_DEVICE_INDEX:
1061       impl.deviceIndex = g_value_get_int (value);
1062       break;
1063     case PROP_DO_STATS:
1064       impl.doStats = g_value_get_boolean (value);
1065       break;
1066     default:
1067       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1068       break;
1069   }
1070 }
1071
1072 static GstStateChangeReturn
1073 gst_avf_video_src_change_state (GstElement * element, GstStateChange transition)
1074 {
1075   GstStateChangeReturn ret;
1076
1077   OBJC_CALLOUT_BEGIN ();
1078   ret = [GST_AVF_VIDEO_SRC_IMPL (element) changeState: transition];
1079   OBJC_CALLOUT_END ();
1080
1081   return ret;
1082 }
1083
1084 static GstCaps *
1085 gst_avf_video_src_get_caps (GstBaseSrc * basesrc, GstCaps * filter)
1086 {
1087   GstCaps *ret;
1088
1089   OBJC_CALLOUT_BEGIN ();
1090   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) getCaps];
1091   OBJC_CALLOUT_END ();
1092
1093   return ret;
1094 }
1095
1096 static gboolean
1097 gst_avf_video_src_set_caps (GstBaseSrc * basesrc, GstCaps * caps)
1098 {
1099   gboolean ret;
1100
1101   OBJC_CALLOUT_BEGIN ();
1102   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) setCaps:caps];
1103   OBJC_CALLOUT_END ();
1104
1105   return ret;
1106 }
1107
1108 static gboolean
1109 gst_avf_video_src_start (GstBaseSrc * basesrc)
1110 {
1111   gboolean ret;
1112
1113   OBJC_CALLOUT_BEGIN ();
1114   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) start];
1115   OBJC_CALLOUT_END ();
1116
1117   return ret;
1118 }
1119
1120 static gboolean
1121 gst_avf_video_src_stop (GstBaseSrc * basesrc)
1122 {
1123   gboolean ret;
1124
1125   OBJC_CALLOUT_BEGIN ();
1126   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) stop];
1127   OBJC_CALLOUT_END ();
1128
1129   return ret;
1130 }
1131
1132 static gboolean
1133 gst_avf_video_src_query (GstBaseSrc * basesrc, GstQuery * query)
1134 {
1135   gboolean ret;
1136
1137   OBJC_CALLOUT_BEGIN ();
1138   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) query:query];
1139   OBJC_CALLOUT_END ();
1140
1141   return ret;
1142 }
1143
1144 static gboolean
1145 gst_avf_video_src_unlock (GstBaseSrc * basesrc)
1146 {
1147   gboolean ret;
1148
1149   OBJC_CALLOUT_BEGIN ();
1150   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) unlock];
1151   OBJC_CALLOUT_END ();
1152
1153   return ret;
1154 }
1155
1156 static gboolean
1157 gst_avf_video_src_unlock_stop (GstBaseSrc * basesrc)
1158 {
1159   gboolean ret;
1160
1161   OBJC_CALLOUT_BEGIN ();
1162   ret = [GST_AVF_VIDEO_SRC_IMPL (basesrc) unlockStop];
1163   OBJC_CALLOUT_END ();
1164
1165   return ret;
1166 }
1167
1168 static GstFlowReturn
1169 gst_avf_video_src_create (GstPushSrc * pushsrc, GstBuffer ** buf)
1170 {
1171   GstFlowReturn ret;
1172
1173   OBJC_CALLOUT_BEGIN ();
1174   ret = [GST_AVF_VIDEO_SRC_IMPL (pushsrc) create: buf];
1175   OBJC_CALLOUT_END ();
1176
1177   return ret;
1178 }