dialog: use new API
[vlc.git] / modules / access / avcapture.m
1 /*****************************************************************************
2  * avcapture.m: AVFoundation (Mac OS X) based video capture module
3  *****************************************************************************
4  * Copyright © 2008-2013 VLC authors and VideoLAN
5  *
6  * Authors: Michael Feurstein <michael.feurstein@gmail.com>
7  *
8  ****************************************************************************
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_input.h>
35 #include <vlc_demux.h>
36 #include <vlc_interface.h>
37 #include <vlc_dialog.h>
38 #include <vlc_access.h>
39
40 #import <AVFoundation/AVFoundation.h>
41 #import <CoreMedia/CoreMedia.h>
42
43 /*****************************************************************************
44 * Local prototypes
45 *****************************************************************************/
46 static int Open(vlc_object_t *p_this);
47 static void Close(vlc_object_t *p_this);
48 static int Demux(demux_t *p_demux);
49 static int Control(demux_t *, int, va_list);
50
51 /*****************************************************************************
52 * Module descriptor
53 *****************************************************************************/
54 vlc_module_begin ()
55    set_shortname(N_("AVFoundation Video Capture"))
56    set_description(N_("AVFoundation video capture module."))
57    set_category(CAT_INPUT)
58    set_subcategory(SUBCAT_INPUT_ACCESS)
59    add_shortcut("avcapture")
60    set_capability("access_demux", 10)
61    set_callbacks(Open, Close)
62 vlc_module_end ()
63
64
65 /*****************************************************************************
66 * AVFoundation Bridge
67 *****************************************************************************/
68 @interface VLCAVDecompressedVideoOutput : AVCaptureVideoDataOutput
69 {
70     demux_t             *p_avcapture;
71
72     CVImageBufferRef    currentImageBuffer;
73
74     mtime_t             currentPts;
75     mtime_t             previousPts;
76     size_t              bytesPerRow;
77
78     long                timeScale;
79     BOOL                videoDimensionsReady;
80 }
81
82 @property (readwrite) CMVideoDimensions videoDimensions;
83
84 - (id)initWithDemux:(demux_t *)p_demux;
85 - (int)width;
86 - (int)height;
87 - (void)getVideoDimensions:(CMSampleBufferRef)sampleBuffer;
88 - (mtime_t)currentPts;
89 - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
90 - (mtime_t)copyCurrentFrameToBuffer:(void *)buffer;
91 @end
92
93 @implementation VLCAVDecompressedVideoOutput : AVCaptureVideoDataOutput
94
95 - (id)initWithDemux:(demux_t *)p_demux
96 {
97     if (self = [super init])
98     {
99         p_avcapture = p_demux;
100         currentImageBuffer = nil;
101         currentPts = 0;
102         previousPts = 0;
103         bytesPerRow = 0;
104         timeScale = 0;
105         videoDimensionsReady = NO;
106     }
107     return self;
108 }
109
110 - (void)dealloc
111 {
112     @synchronized (self)
113     {
114         CVBufferRelease(currentImageBuffer);
115         currentImageBuffer = nil;
116         bytesPerRow = 0;
117         videoDimensionsReady = NO;
118     }
119     [super dealloc];
120 }
121
122 - (long)timeScale
123 {
124     return timeScale;
125 }
126
127 - (int)width
128 {
129     return self.videoDimensions.width;
130 }
131
132 - (int)height
133 {
134     return self.videoDimensions.height;
135 }
136
137 - (size_t)bytesPerRow
138 {
139     return bytesPerRow;
140 }
141
142 - (void)getVideoDimensions:(CMSampleBufferRef)sampleBuffer
143 {
144     if (!videoDimensionsReady)
145     {
146         CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
147         self.videoDimensions = CMVideoFormatDescriptionGetDimensions(formatDescription);
148         bytesPerRow = CVPixelBufferGetBytesPerRow(CMSampleBufferGetImageBuffer(sampleBuffer));
149         videoDimensionsReady = YES;
150         msg_Dbg(p_avcapture, "Dimensionns obtained height:%i width:%i bytesPerRow:%lu", [self height], [self width], bytesPerRow);
151     }
152 }
153
154 -(mtime_t)currentPts
155 {
156     mtime_t pts;
157
158     if ( !currentImageBuffer || currentPts == previousPts )
159         return 0;
160
161     @synchronized (self)
162     {
163         pts = previousPts = currentPts;
164     }
165
166     return currentPts;
167 }
168
169 - (void)captureOutput:(AVCaptureOutput *)captureOutput
170 didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
171        fromConnection:(AVCaptureConnection *)connection
172 {
173     @autoreleasepool {
174         CVImageBufferRef imageBufferToRelease;
175         CMTime presentationtimestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
176         CVImageBufferRef videoFrame = CMSampleBufferGetImageBuffer(sampleBuffer);
177         CVBufferRetain(videoFrame);
178         [self getVideoDimensions:sampleBuffer];
179
180         @synchronized (self) {
181             imageBufferToRelease = currentImageBuffer;
182             currentImageBuffer = videoFrame;
183             currentPts = (mtime_t)presentationtimestamp.value;
184             timeScale = (long)presentationtimestamp.timescale;
185         }
186         
187         CVBufferRelease(imageBufferToRelease);
188     }
189 }
190
191 - (mtime_t)copyCurrentFrameToBuffer:(void *)buffer
192 {
193     CVImageBufferRef imageBuffer;
194     mtime_t pts;
195
196     void *pixels;
197
198     if ( !currentImageBuffer || currentPts == previousPts )
199         return 0;
200
201     @synchronized (self)
202     {
203         imageBuffer = CVBufferRetain(currentImageBuffer);
204         if (imageBuffer)
205         {
206             pts = previousPts = currentPts;
207             CVPixelBufferLockBaseAddress(imageBuffer, 0);
208             pixels = CVPixelBufferGetBaseAddress(imageBuffer);
209             if (pixels)
210             {
211                 memcpy(buffer, pixels, CVPixelBufferGetHeight(imageBuffer) * CVPixelBufferGetBytesPerRow(imageBuffer));
212             }
213             CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
214         }
215     }
216     CVBufferRelease(imageBuffer);
217
218     if (pixels)
219         return currentPts;
220     else
221         return 0;
222 }
223
224 @end
225
226 /*****************************************************************************
227 * Struct
228 *****************************************************************************/
229
230 struct demux_sys_t
231 {
232     AVCaptureSession                *session;
233     AVCaptureDevice                 *device;
234     VLCAVDecompressedVideoOutput    *output;
235     es_out_id_t                     *p_es_video;
236     es_format_t                     fmt;
237     int                             height, width;
238     BOOL                            b_es_setup;
239 };
240
241 /*****************************************************************************
242 * Open:
243 *****************************************************************************/
244 static int Open(vlc_object_t *p_this)
245 {
246     demux_t                 *p_demux = (demux_t*)p_this;
247     demux_sys_t             *p_sys = NULL;
248
249     NSString                *avf_currdevice_uid;
250     NSArray                 *myVideoDevices;
251     NSError                 *o_returnedError;
252
253     AVCaptureDeviceInput    *input = nil;
254
255     int                     i, i_width, i_height, deviceCount, ivideo;
256
257     char                    *psz_uid = NULL;
258
259     /* Only when selected */
260     if ( *p_demux->psz_access == '\0' )
261         return VLC_EGENERIC;
262
263     @autoreleasepool {
264         if (p_demux->psz_location && *p_demux->psz_location)
265             psz_uid = strdup(p_demux->psz_location);
266
267         msg_Dbg(p_demux, "avcapture uid = %s", psz_uid);
268         avf_currdevice_uid = [[NSString alloc] initWithFormat:@"%s", psz_uid];
269
270         /* Set up p_demux */
271         p_demux->pf_demux = Demux;
272         p_demux->pf_control = Control;
273         p_demux->info.i_update = 0;
274         p_demux->info.i_title = 0;
275         p_demux->info.i_seekpoint = 0;
276
277         p_demux->p_sys = p_sys = calloc(1, sizeof(demux_sys_t));
278         if ( !p_sys )
279             return VLC_ENOMEM;
280
281         myVideoDevices = [[[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]
282                            arrayByAddingObjectsFromArray:[AVCaptureDevice devicesWithMediaType:AVMediaTypeMuxed]] retain];
283         if ( [myVideoDevices count] == 0 )
284         {
285             vlc_dialog_display_error(p_demux, _("No video devices found"),
286                 _("Your Mac does not seem to be equipped with a suitable video input device. "
287                 "Please check your connectors and drivers."));
288             msg_Err(p_demux, "Can't find any suitable video device");
289             goto error;
290         }
291
292         deviceCount = [myVideoDevices count];
293         for ( ivideo = 0; ivideo < deviceCount; ivideo++ )
294         {
295             AVCaptureDevice *avf_device;
296             avf_device = [myVideoDevices objectAtIndex:ivideo];
297             msg_Dbg(p_demux, "avcapture %i/%i %s %s", ivideo, deviceCount, [[avf_device modelID] UTF8String], [[avf_device uniqueID] UTF8String]);
298             if ([[[avf_device uniqueID]stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:avf_currdevice_uid]) {
299                 break;
300             }
301         }
302
303         if ( ivideo < [myVideoDevices count] )
304         {
305             p_sys->device = [myVideoDevices objectAtIndex:ivideo];
306         }
307         else
308         {
309             msg_Dbg(p_demux, "Cannot find designated device as %s, falling back to default.", [avf_currdevice_uid UTF8String]);
310             p_sys->device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
311         }
312         if ( !p_sys->device )
313         {
314             vlc_dialog_display_error(p_demux, _("No video devices found"),
315                 _("Your Mac does not seem to be equipped with a suitable input device. "
316                 "Please check your connectors and drivers."));
317             msg_Err(p_demux, "Can't find any suitable video device");
318             goto error;
319         }
320
321         if ( [p_sys->device isInUseByAnotherApplication] == YES )
322         {
323             msg_Err(p_demux, "default capture device is exclusively in use by another application");
324             goto error;
325         }
326
327         input = [AVCaptureDeviceInput deviceInputWithDevice:p_sys->device error:&o_returnedError];
328
329         if ( !input )
330         {
331             msg_Err(p_demux, "can't create a valid capture input facility (%ld)", [o_returnedError code]);
332             goto error;
333         }
334
335         int chroma = VLC_CODEC_RGB32;
336
337         memset(&p_sys->fmt, 0, sizeof(es_format_t));
338         es_format_Init(&p_sys->fmt, VIDEO_ES, chroma);
339
340         p_sys->session = [[AVCaptureSession alloc] init];
341         [p_sys->session addInput:input];
342
343         p_sys->output = [[VLCAVDecompressedVideoOutput alloc] initWithDemux:p_demux];
344         [p_sys->session addOutput:p_sys->output];
345
346         dispatch_queue_t queue = dispatch_queue_create("avCaptureQueue", NULL);
347         [p_sys->output setSampleBufferDelegate:(id)p_sys->output queue:queue];
348         dispatch_release(queue);
349
350         p_sys->output.videoSettings = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey];
351         [p_sys->session startRunning];
352         
353         [input release];
354         
355         msg_Dbg(p_demux, "AVCapture: Video device ready!");
356         
357         return VLC_SUCCESS;
358     error:
359         msg_Err(p_demux, "Error");
360         [input release];
361         
362         free(p_sys);
363         
364         return VLC_EGENERIC;
365     }
366 }
367
368 /*****************************************************************************
369 * Close:
370 *****************************************************************************/
371 static void Close(vlc_object_t *p_this)
372 {
373     demux_t             *p_demux = (demux_t*)p_this;
374     demux_sys_t         *p_sys = p_demux->p_sys;
375
376     @autoreleasepool {
377         msg_Dbg(p_demux,"Close AVCapture");
378
379         // Perform this on main thread, as the framework itself will sometimes try to synchronously
380         // work on main thread. And this will create a dead lock.
381         [p_sys->session performSelectorOnMainThread:@selector(stopRunning) withObject:nil waitUntilDone:NO];
382         [p_sys->output performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];
383         [p_sys->session performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];
384         
385         free(p_sys);
386     }
387 }
388
389 /*****************************************************************************
390 * Demux:
391 *****************************************************************************/
392 static int Demux(demux_t *p_demux)
393 {
394     demux_sys_t *p_sys = p_demux->p_sys;
395     block_t     *p_block;
396
397     @autoreleasepool {
398         @synchronized ( p_sys->output )
399         {
400             p_block = block_Alloc([p_sys->output width] * [p_sys->output bytesPerRow]);
401
402             if ( !p_block )
403             {
404                 msg_Err(p_demux, "cannot get block");
405                 return 0;
406             }
407
408             p_block->i_pts = [p_sys->output copyCurrentFrameToBuffer: p_block->p_buffer];
409
410             if ( !p_block->i_pts )
411             {
412                 /* Nothing to display yet, just forget */
413                 block_Release(p_block);
414                 msleep(10000);
415                 return 1;
416             }
417             else if ( !p_sys->b_es_setup )
418             {
419                 p_sys->fmt.video.i_frame_rate_base = [p_sys->output timeScale];
420                 msg_Dbg(p_demux, "using frame rate base: %i", p_sys->fmt.video.i_frame_rate_base);
421                 p_sys->width = p_sys->fmt.video.i_width = [p_sys->output width];
422                 p_sys->height = p_sys->fmt.video.i_height = [p_sys->output height];
423                 p_sys->p_es_video = es_out_Add(p_demux->out, &p_sys->fmt);
424                 msg_Dbg(p_demux, "added new video es %4.4s %dx%d", (char*)&p_sys->fmt.i_codec, p_sys->width, p_sys->height);
425                 p_sys->b_es_setup = YES;
426             }
427         }
428         
429         es_out_Control(p_demux->out, ES_OUT_SET_PCR, p_block->i_pts);
430         es_out_Send(p_demux->out, p_sys->p_es_video, p_block);
431         
432     }
433     return 1;
434 }
435
436 /*****************************************************************************
437 * Control:
438 *****************************************************************************/
439 static int Control(demux_t *p_demux, int i_query, va_list args)
440 {
441     bool        *pb;
442     int64_t     *pi64;
443
444     switch( i_query )
445     {
446         /* Special for access_demux */
447         case DEMUX_CAN_PAUSE:
448         case DEMUX_CAN_SEEK:
449         case DEMUX_SET_PAUSE_STATE:
450         case DEMUX_CAN_CONTROL_PACE:
451            pb = (bool*)va_arg(args, bool *);
452            *pb = false;
453            return VLC_SUCCESS;
454
455         case DEMUX_GET_PTS_DELAY:
456            pi64 = (int64_t*)va_arg(args, int64_t *);
457            *pi64 = INT64_C(1000) * var_InheritInteger(p_demux, "live-caching");
458            return VLC_SUCCESS;
459
460         case DEMUX_GET_TIME:
461             pi64 = (int64_t*)va_arg(args, int64_t *);
462             *pi64 = mdate();
463             return VLC_SUCCESS;
464
465         default:
466            return VLC_EGENERIC;
467     }
468     return VLC_EGENERIC;
469 }