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