qtcapture.m 13.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
/*****************************************************************************
* qtcapture.m: qtkit (Mac OS X) based capture module
*****************************************************************************
* Copyright (C) 2008 the VideoLAN team
*
* Authors: Pierre d'Herbemont <pdherbemont@videolan.org>
*
*****************************************************************************
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
*****************************************************************************/

/*****************************************************************************
* Preamble
*****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

33
#include <vlc_common.h>
34 35 36
#include <vlc_plugin.h>
#include <vlc_input.h>
#include <vlc_demux.h>
37
#include <vlc_interface.h>
Rémi Duraffort's avatar
Rémi Duraffort committed
38
#include <vlc_dialog.h>
39 40

#import <QTKit/QTKit.h>
41
#import <CoreAudio/CoreAudio.h>
42 43 44 45 46 47 48 49 50 51 52 53

/*****************************************************************************
* Local prototypes
*****************************************************************************/
static int Open( vlc_object_t *p_this );
static void Close( vlc_object_t *p_this );
static int Demux( demux_t *p_demux );
static int Control( demux_t *, int, va_list );

/*****************************************************************************
* Module descriptor
*****************************************************************************/
54 55 56 57 58 59 60 61 62
vlc_module_begin ()
   set_shortname( N_("Quicktime Capture") )
   set_description( N_("Quicktime Capture") )
   set_category( CAT_INPUT )
   set_subcategory( SUBCAT_INPUT_ACCESS )
   add_shortcut( "qtcapture" )
   set_capability( "access_demux", 10 )
   set_callbacks( Open, Close )
vlc_module_end ()
63 64 65 66 67 68 69


/*****************************************************************************
* QTKit Bridge
*****************************************************************************/
@interface VLCDecompressedVideoOutput : QTCaptureDecompressedVideoOutput
{
70 71
    CVImageBufferRef currentImageBuffer;
    mtime_t currentPts;
72
    mtime_t previousPts;
73 74 75
}
- (id)init;
- (void)outputVideoFrame:(CVImageBufferRef)videoFrame withSampleBuffer:(QTSampleBuffer *)sampleBuffer fromConnection:(QTCaptureConnection *)connection;
76
- (mtime_t)copyCurrentFrameToBuffer:(void *)buffer;
77 78 79 80 81 82
@end

/* Apple sample code */
@implementation VLCDecompressedVideoOutput : QTCaptureDecompressedVideoOutput
- (id)init
{
83 84 85 86
    if( self = [super init] )
    {
        currentImageBuffer = nil;
        currentPts = 0;
87
        previousPts = 0;
88 89
    }
    return self;
90 91 92
}
- (void)dealloc
{
93 94
    @synchronized (self)
    {
95 96 97 98
        CVBufferRelease(currentImageBuffer);
        currentImageBuffer = nil;
    }
    [super dealloc];
99 100 101 102
}

- (void)outputVideoFrame:(CVImageBufferRef)videoFrame withSampleBuffer:(QTSampleBuffer *)sampleBuffer fromConnection:(QTCaptureConnection *)connection
{
103 104 105
    // Store the latest frame
    // This must be done in a @synchronized block because this delegate method is not called on the main thread
    CVImageBufferRef imageBufferToRelease;
106

107
    CVBufferRetain(videoFrame);
108

109 110
    @synchronized (self)
    {
111 112
        imageBufferToRelease = currentImageBuffer;
        currentImageBuffer = videoFrame;
113 114 115 116 117
        currentPts = (mtime_t)(1000000L / [sampleBuffer presentationTime].timeScale * [sampleBuffer presentationTime].timeValue);
        
        /* Try to use hosttime of the sample if available, because iSight Pts seems broken */
        NSNumber *hosttime = (NSNumber *)[sampleBuffer attributeForKey:QTSampleBufferHostTimeAttribute];
        if( hosttime ) currentPts = (mtime_t)AudioConvertHostTimeToNanos([hosttime unsignedLongLongValue])/1000;
118 119
    }
    CVBufferRelease(imageBufferToRelease);
120 121
}

122
- (mtime_t)copyCurrentFrameToBuffer:(void *)buffer
123
{
124 125
    CVImageBufferRef imageBuffer;
    mtime_t pts;
126

127
    if(!currentImageBuffer || currentPts == previousPts )
128
        return 0;
129 130 131

    @synchronized (self)
    {
132
        imageBuffer = CVBufferRetain(currentImageBuffer);
133
        pts = previousPts = currentPts;
134

135 136
        CVPixelBufferLockBaseAddress(imageBuffer, 0);
        void * pixels = CVPixelBufferGetBaseAddress(imageBuffer);
137 138
        memcpy( buffer, pixels, CVPixelBufferGetBytesPerRow(imageBuffer) * CVPixelBufferGetHeight(imageBuffer) );
        CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
139
    }
140

141 142 143
    CVBufferRelease(imageBuffer);

    return currentPts;
144 145 146 147
}

@end

148 149 150 151
/*****************************************************************************
* Struct
*****************************************************************************/

152
struct demux_sys_t {
153 154 155 156 157
    QTCaptureSession * session;
    QTCaptureDevice * device;
    VLCDecompressedVideoOutput * output;
    int height, width;
    es_out_id_t * p_es_video;
158 159 160
};


161 162 163 164
/*****************************************************************************
* qtchroma_to_fourcc
*****************************************************************************/
static int qtchroma_to_fourcc( int i_qt )
165
{
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
166
    static const struct
167 168 169 170 171 172
    {
        unsigned int i_qt;
        int i_fourcc;
    } qtchroma_to_fourcc[] =
    {
        /* Raw data types */
173 174 175
        { '2vuy',    VLC_CODEC_UYVY },
        { 'yuv2',VLC_CODEC_YUYV },
        { 'yuvs', VLC_CODEC_YUYV },
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
        { 0, 0 }
    };
    int i;
    for( i = 0; qtchroma_to_fourcc[i].i_qt; i++ )
    {
        if( qtchroma_to_fourcc[i].i_qt == i_qt )
            return qtchroma_to_fourcc[i].i_fourcc;
    }
    return 0;
}

/*****************************************************************************
* Open:
*****************************************************************************/
static int Open( vlc_object_t *p_this )
{
    demux_t     *p_demux = (demux_t*)p_this;
    demux_sys_t *p_sys = NULL;
    es_format_t fmt;
    int i;
    int i_width;
    int i_height;
    int result = 0;

200 201 202
    /* Only when selected */
    if( *p_demux->psz_access == '\0' )
        return VLC_EGENERIC;
203
    
204 205
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

206 207 208 209 210 211 212
    /* Set up p_demux */
    p_demux->pf_demux = Demux;
    p_demux->pf_control = Control;
    p_demux->info.i_update = 0;
    p_demux->info.i_title = 0;
    p_demux->info.i_seekpoint = 0;
    
213 214 215
    p_demux->p_sys = p_sys = calloc( 1, sizeof( demux_sys_t ) );
    if( !p_sys )
        return VLC_ENOMEM;
216 217 218
    
    memset( &fmt, 0, sizeof( es_format_t ) );    
    
219
    QTCaptureDeviceInput * input = nil;
220
    NSError *o_returnedError;
221

222 223
    p_sys->device = [QTCaptureDevice defaultInputDeviceWithMediaType: QTMediaTypeVideo];
    if( !p_sys->device )
224
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
225
        dialog_FatalWait( p_demux, _("No Input device found"),
226 227
                        _("Your Mac does not seem to be equipped with a suitable input device. "
                          "Please check your connectors and drivers.") );
228
        msg_Err( p_demux, "Can't find any Video device" );
229
        
230 231 232
        goto error;
    }

233
    if( ![p_sys->device open: &o_returnedError] )
234
    {
235
        msg_Err( p_demux, "Unable to open the capture device (%ld)", [o_returnedError code] );
236 237 238 239 240 241
        goto error;
    }

    if( [p_sys->device isInUseByAnotherApplication] == YES )
    {
        msg_Err( p_demux, "default capture device is exclusively in use by another application" );
242 243 244
        goto error;
    }

245
    input = [[QTCaptureDeviceInput alloc] initWithDevice: p_sys->device];
246
    if( !input )
247
    {
248
        msg_Err( p_demux, "can't create a valid capture input facility" );
249 250 251
        goto error;
    }

252
    p_sys->output = [[VLCDecompressedVideoOutput alloc] init];
253

254 255 256
    /* Get the formats */
    NSArray *format_array = [p_sys->device formatDescriptions];
    QTFormatDescription* camera_format = NULL;
257
    for( int k = 0; k < [format_array count]; k++ )
258 259 260
    {
        camera_format = [format_array objectAtIndex: k];

261 262
        NSLog( @"%@", [camera_format localizedFormatSummary] );
        NSLog( @"%@",[[camera_format formatDescriptionAttributes] description] );
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
    }
    if( [format_array count] )
        camera_format = [format_array objectAtIndex: 0];
    else goto error;

    int qtchroma = [camera_format formatType];
    int chroma = qtchroma_to_fourcc( qtchroma );
    if( !chroma )
    {
        msg_Err( p_demux, "Unknown qt chroma %4.4s provided by camera", (char*)&qtchroma );
        goto error;
    }

    /* Now we can init */
    es_format_Init( &fmt, VIDEO_ES, chroma );

    NSSize encoded_size = [[camera_format attributeForKey:QTFormatDescriptionVideoEncodedPixelsSizeAttribute] sizeValue];
    NSSize display_size = [[camera_format attributeForKey:QTFormatDescriptionVideoCleanApertureDisplaySizeAttribute] sizeValue];
    NSSize par_size = [[camera_format attributeForKey:QTFormatDescriptionVideoProductionApertureDisplaySizeAttribute] sizeValue];

    fmt.video.i_width = p_sys->width = encoded_size.width;
    fmt.video.i_height = p_sys->height = encoded_size.height;
    if( par_size.width != encoded_size.width )
    {
Laurent Aimar's avatar
Laurent Aimar committed
287 288
        fmt.video.i_sar_num = (int64_t)encoded_size.height * par_size.width / encoded_size.width;
        fmt.video.i_sar_den = encoded_size.width;
289 290 291 292 293 294
    }

    NSLog( @"encoded_size %d %d", (int)encoded_size.width, (int)encoded_size.height );
    NSLog( @"display_size %d %d", (int)display_size.width, (int)display_size.height );
    NSLog( @"PAR size %d %d", (int)par_size.width, (int)par_size.height );
    
295
    [p_sys->output setPixelBufferAttributes: [NSDictionary dictionaryWithObjectsAndKeys:
296 297 298 299
        [NSNumber numberWithInt: p_sys->height], kCVPixelBufferHeightKey,
        [NSNumber numberWithInt: p_sys->width], kCVPixelBufferWidthKey,
        [NSNumber numberWithBool:YES], (id)kCVPixelBufferOpenGLCompatibilityKey,
        nil]];
300

301
    p_sys->session = [[QTCaptureSession alloc] init];
302

303
    bool ret = [p_sys->session addInput:input error: &o_returnedError];
304 305
    if( !ret )
    {
306
        msg_Err( p_demux, "default video capture device could not be added to capture session (%ld)", [o_returnedError code] );
307 308 309
        goto error;
    }

310
    ret = [p_sys->session addOutput:p_sys->output error: &o_returnedError];
311 312
    if( !ret )
    {
313
        msg_Err( p_demux, "output could not be added to capture session (%ld)", [o_returnedError code] );
314 315 316
        goto error;
    }

317
    [p_sys->session startRunning];
318

319
    msg_Dbg( p_demux, "added new video es %4.4s %dx%d",
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
            (char*)&fmt.i_codec, fmt.video.i_width, fmt.video.i_height );

    p_sys->p_es_video = es_out_Add( p_demux->out, &fmt );

    [input release];
    [pool release];

    msg_Dbg( p_demux, "QTCapture: We have a video device ready!" );

    return VLC_SUCCESS;
error:
    [input release];
    [pool release];

    free( p_sys );

    return VLC_EGENERIC;
}

/*****************************************************************************
* Close:
*****************************************************************************/
static void Close( vlc_object_t *p_this )
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    demux_t     *p_demux = (demux_t*)p_this;
    demux_sys_t *p_sys = p_demux->p_sys;
348

349 350 351 352 353 354 355 356 357
    /* Hack: if libvlc was killed, main interface thread was,
     * and poor QTKit needs it, so don't tell him.
     * Else we dead lock. */
    if( vlc_object_alive(p_this->p_libvlc))
    {
        [p_sys->session stopRunning];
        [p_sys->output release];
        [p_sys->session release];
    }
358 359 360 361 362 363 364 365 366 367 368 369 370 371
    free( p_sys );

    [pool release];
}


/*****************************************************************************
* Demux:
*****************************************************************************/
static int Demux( demux_t *p_demux )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    block_t *p_block;

372 373 374 375 376 377 378 379
    p_block = block_New( p_demux, p_sys->width *
                            p_sys->height * 2 /* FIXME */ );
    if( !p_block )
    {
        msg_Err( p_demux, "cannot get block" );
        return 0;
    }

380 381
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

382 383
    @synchronized (p_sys->output)
    {
384
    p_block->i_pts = [p_sys->output copyCurrentFrameToBuffer: p_block->p_buffer];
385
    }
386

387
    if( !p_block->i_pts )
388 389
    {
        /* Nothing to display yet, just forget */
390
        block_Release( p_block );
391
        [pool release];
392
        msleep( 10000 );
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431
        return 1;
    }

    es_out_Control( p_demux->out, ES_OUT_SET_PCR, p_block->i_pts );
    es_out_Send( p_demux->out, p_sys->p_es_video, p_block );

    [pool release];
    return 1;
}

/*****************************************************************************
* Control:
*****************************************************************************/
static int Control( demux_t *p_demux, int i_query, va_list args )
{
    bool *pb;
    int64_t    *pi64;

    switch( i_query )
    {
        /* Special for access_demux */
        case DEMUX_CAN_PAUSE:
        case DEMUX_CAN_SEEK:
        case DEMUX_SET_PAUSE_STATE:
        case DEMUX_CAN_CONTROL_PACE:
           pb = (bool*)va_arg( args, bool * );
           *pb = false;
           return VLC_SUCCESS;

        case DEMUX_GET_PTS_DELAY:
           pi64 = (int64_t*)va_arg( args, int64_t * );
           *pi64 = (int64_t)DEFAULT_PTS_DELAY;
           return VLC_SUCCESS;

        default:
           return VLC_EGENERIC;
    }
    return VLC_EGENERIC;
}