X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Faccess%2Fqtcapture.m;h=c27164784731ddd6156eb0ce571fff44fc843305;hb=dfae18e3d74bf16571411dea25afb25bfbf397d6;hp=a39b7a2fb0e913fb251b07079e4f7643beed9e35;hpb=90d9d6dda3dc1a6260ac5e3eb355c83b377da201;p=vlc diff --git a/modules/access/qtcapture.m b/modules/access/qtcapture.m index a39b7a2fb0..c271647847 100644 --- a/modules/access/qtcapture.m +++ b/modules/access/qtcapture.m @@ -1,7 +1,7 @@ /***************************************************************************** * qtcapture.m: qtkit (Mac OS X) based capture module ***************************************************************************** -* Copyright (C) 2008 the VideoLAN team +* Copyright (C) 2008-2011 the VideoLAN team * * Authors: Pierre d'Herbemont * @@ -30,13 +30,21 @@ # include "config.h" #endif -#include +#include #include #include -#include #include +#include +#include +#include #import +#import + +#define QTKIT_WIDTH_TEXT N_("Video Capture width") +#define QTKIT_WIDTH_LONGTEXT N_("Video Capture width in pixel") +#define QTKIT_HEIGHT_TEXT N_("Video Capture height") +#define QTKIT_HEIGHT_LONGTEXT N_("Video Capture height in pixel") /***************************************************************************** * Local prototypes @@ -49,15 +57,19 @@ static int Control( demux_t *, int, va_list ); /***************************************************************************** * Module descriptor *****************************************************************************/ -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(); +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 ) + add_integer("qtcapture-width", 640, QTKIT_WIDTH_TEXT, QTKIT_WIDTH_LONGTEXT, true) + change_integer_range (80, 1280) + add_integer("qtcapture-height", 480, QTKIT_HEIGHT_TEXT, QTKIT_HEIGHT_LONGTEXT, true) + change_integer_range (60, 480) +vlc_module_end () /***************************************************************************** @@ -65,64 +77,86 @@ vlc_module_end(); *****************************************************************************/ @interface VLCDecompressedVideoOutput : QTCaptureDecompressedVideoOutput { - CVImageBufferRef currentImageBuffer; + CVImageBufferRef currentImageBuffer; + mtime_t currentPts; + mtime_t previousPts; } - (id)init; - (void)outputVideoFrame:(CVImageBufferRef)videoFrame withSampleBuffer:(QTSampleBuffer *)sampleBuffer fromConnection:(QTCaptureConnection *)connection; -- (BOOL)copyCurrentFrameToBuffer:(void *)buffer; +- (mtime_t)copyCurrentFrameToBuffer:(void *)buffer; @end /* Apple sample code */ @implementation VLCDecompressedVideoOutput : QTCaptureDecompressedVideoOutput - (id)init { - if( self = [super init] ) - { - currentImageBuffer = nil; - } - return self; + if( self = [super init] ) + { + currentImageBuffer = nil; + currentPts = 0; + previousPts = 0; + } + return self; } - (void)dealloc { - @synchronized (self) { - CVBufferRelease(currentImageBuffer); - currentImageBuffer = nil; - } - [super dealloc]; + @synchronized (self) + { + CVBufferRelease(currentImageBuffer); + currentImageBuffer = nil; + } + [super dealloc]; } - (void)outputVideoFrame:(CVImageBufferRef)videoFrame withSampleBuffer:(QTSampleBuffer *)sampleBuffer fromConnection:(QTCaptureConnection *)connection { - // 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; + // 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; + + CVBufferRetain(videoFrame); - CVBufferRetain(videoFrame); + @synchronized (self) + { + imageBufferToRelease = currentImageBuffer; + currentImageBuffer = videoFrame; + currentPts = (mtime_t)(1000000L / [sampleBuffer presentationTime].timeScale * [sampleBuffer presentationTime].timeValue); - @synchronized (self) { - imageBufferToRelease = currentImageBuffer; - currentImageBuffer = videoFrame; - } - CVBufferRelease(imageBufferToRelease); + /* 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; + } + CVBufferRelease(imageBufferToRelease); } -- (BOOL)copyCurrentFrameToBuffer:(void *)buffer +- (mtime_t)copyCurrentFrameToBuffer:(void *)buffer { - CVImageBufferRef imageBuffer; - - @synchronized (self) { - if(!currentImageBuffer) return NO; - imageBuffer = CVBufferRetain(currentImageBuffer); - } + CVImageBufferRef imageBuffer; + mtime_t pts; + void * pixels; - CVPixelBufferLockBaseAddress(imageBuffer, 0); - void * pixels = CVPixelBufferGetBaseAddress(imageBuffer); - memcpy( buffer, pixels, CVPixelBufferGetBytesPerRow(imageBuffer) * CVPixelBufferGetHeight(imageBuffer) ); - CVPixelBufferUnlockBaseAddress(imageBuffer, 0); + if(!currentImageBuffer || currentPts == previousPts ) + return 0; - CVBufferRelease(imageBuffer); + @synchronized (self) + { + imageBuffer = CVBufferRetain(currentImageBuffer); + if(imageBuffer){ + pts = previousPts = currentPts; + CVPixelBufferLockBaseAddress(imageBuffer, 0); + pixels = CVPixelBufferGetBaseAddress(imageBuffer); + if(pixels) + memcpy( buffer, pixels, CVPixelBufferGetBytesPerRow(imageBuffer) * CVPixelBufferGetHeight(imageBuffer)); + CVPixelBufferUnlockBaseAddress(imageBuffer, 0); + } + + } + CVBufferRelease(imageBuffer); - return YES; + if(pixels) + return currentPts; + else + return 0; } @end @@ -132,10 +166,11 @@ vlc_module_end(); *****************************************************************************/ struct demux_sys_t { - QTCaptureSession * session; - VLCDecompressedVideoOutput * output; - int height, width; - es_out_id_t * p_es_video; + QTCaptureSession * session; + QTCaptureDevice * device; + VLCDecompressedVideoOutput * output; + int height, width; + es_out_id_t * p_es_video; }; @@ -144,14 +179,16 @@ struct demux_sys_t { *****************************************************************************/ static int qtchroma_to_fourcc( int i_qt ) { - static struct + static const struct { unsigned int i_qt; int i_fourcc; } qtchroma_to_fourcc[] = { /* Raw data types */ - { k422YpCbCr8CodecType, VLC_FOURCC('U','Y','V','Y') }, + { '2vuy', VLC_CODEC_UYVY }, + { 'yuv2',VLC_CODEC_YUYV }, + { 'yuvs', VLC_CODEC_YUYV }, { 0, 0 } }; int i; @@ -174,8 +211,19 @@ static int Open( vlc_object_t *p_this ) int i; int i_width; int i_height; - int i_aspect; int result = 0; + char *psz_uid = NULL; + + /* Only when selected */ + if( *p_demux->psz_access == '\0' ) + return VLC_EGENERIC; + + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + + if( p_demux->psz_location && *p_demux->psz_location ) + psz_uid = strdup(p_demux->psz_location); + msg_Dbg( p_demux, "qtcapture uid = %s", psz_uid ); + NSString *qtk_currdevice_uid = [[NSString alloc] initWithFormat:@"%s", psz_uid]; /* Set up p_demux */ p_demux->pf_demux = Demux; @@ -184,85 +232,145 @@ static int Open( vlc_object_t *p_this ) p_demux->info.i_title = 0; p_demux->info.i_seekpoint = 0; - NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + p_demux->p_sys = p_sys = calloc( 1, sizeof( demux_sys_t ) ); + if( !p_sys ) + return VLC_ENOMEM; - msg_Dbg( p_demux, "QTCapture Probed" ); + NSArray *myVideoDevices = [[[QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo] arrayByAddingObjectsFromArray:[QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeMuxed]] retain]; + if([myVideoDevices count] == 0) + { + dialog_FatalWait( p_demux, _("No Input device found"), + _("Your Mac does not seem to be equipped with a suitable input device. " + "Please check your connectors and drivers.") ); + msg_Err( p_demux, "Can't find any Video device" ); - p_demux->p_sys = p_sys = malloc( sizeof( demux_sys_t ) ); - if( !p_sys ) return VLC_ENOMEM; + goto error; + } + int ivideo; + for(ivideo = 0; ivideo < [myVideoDevices count]; ivideo++){ + QTCaptureDevice *qtk_device; + qtk_device = [myVideoDevices objectAtIndex:ivideo]; + msg_Dbg( p_demux, "qtcapture %d/%lu %s %s", ivideo, [myVideoDevices count], [[qtk_device localizedDisplayName] UTF8String], [[qtk_device uniqueID] UTF8String]); + if([[[qtk_device uniqueID]stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:qtk_currdevice_uid]){ + break; + } + } - memset( p_sys, 0, sizeof( demux_sys_t ) ); memset( &fmt, 0, sizeof( es_format_t ) ); QTCaptureDeviceInput * input = nil; - QTCaptureSession * session = nil; - VLCDecompressedVideoOutput * output = nil; + NSError *o_returnedError; + if( ivideo < [myVideoDevices count] ) + p_sys->device = [myVideoDevices objectAtIndex:ivideo]; + else + { + /* cannot found designated device, fall back to open default device */ + msg_Dbg(p_demux, "Cannot find designated uid device as %s, falling back to default.", [qtk_currdevice_uid UTF8String]); + p_sys->device = [QTCaptureDevice defaultInputDeviceWithMediaType: QTMediaTypeVideo]; + } + if( !p_sys->device ) + { + dialog_FatalWait( p_demux, _("No Input device found"), + _("Your Mac does not seem to be equipped with a suitable input device. " + "Please check your connectors and drivers.") ); + msg_Err( p_demux, "Can't find any Video device" ); - QTCaptureDevice * device = [QTCaptureDevice defaultInputDeviceWithMediaType: QTMediaTypeVideo]; - if( !device ) + goto error; + } + + if( ![p_sys->device open: &o_returnedError] ) { - msg_Err( p_demux, "Can't open any Video device" ); + msg_Err( p_demux, "Unable to open the capture device (%ld)", [o_returnedError code] ); goto error; } - if( ![device open: nil /* FIXME */] ) + if( [p_sys->device isInUseByAnotherApplication] == YES ) { - msg_Err( p_demux, "Can't open any Video device" ); + msg_Err( p_demux, "default capture device is exclusively in use by another application" ); goto error; } - input = [[QTCaptureDeviceInput alloc] initWithDevice: device]; - if( !device ) + input = [[QTCaptureDeviceInput alloc] initWithDevice: p_sys->device]; + if( !input ) { - msg_Err( p_demux, "Can't create a capture session" ); + msg_Err( p_demux, "can't create a valid capture input facility" ); goto error; } - output = [[VLCDecompressedVideoOutput alloc] init]; + p_sys->output = [[VLCDecompressedVideoOutput alloc] init]; + + /* Get the formats */ + NSArray *format_array = [p_sys->device formatDescriptions]; + QTFormatDescription* camera_format = NULL; + for( int k = 0; k < [format_array count]; k++ ) + { + camera_format = [format_array objectAtIndex: k]; + + msg_Dbg(p_demux, "localized Format: %s", [[camera_format localizedFormatSummary] UTF8String] ); + msg_Dbg(p_demux, "format description: %s", [[[camera_format formatDescriptionAttributes] description] UTF8String] ); + } + if( [format_array count] ) + camera_format = [format_array objectAtIndex: 0]; + else goto error; + + int qtchroma = [camera_format formatType]; + int chroma = VLC_CODEC_UYVY; - session = [[QTCaptureSession alloc] init]; + /* Now we can init */ + es_format_Init( &fmt, VIDEO_ES, chroma ); - bool ret = [session addInput:input error:nil /* FIXME */]; - if( !ret ) + NSSize encoded_size = [[camera_format attributeForKey:QTFormatDescriptionVideoEncodedPixelsSizeAttribute] sizeValue]; + NSSize display_size = [[camera_format attributeForKey:QTFormatDescriptionVideoCleanApertureDisplaySizeAttribute] sizeValue]; + NSSize par_size = [[camera_format attributeForKey:QTFormatDescriptionVideoProductionApertureDisplaySizeAttribute] sizeValue]; + + par_size.width = display_size.width = encoded_size.width + = var_CreateGetInteger (p_this, "qtcapture-width"); + par_size.height = display_size.height = encoded_size.height + = var_CreateGetInteger (p_this, "qtcapture-height"); + + 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 ) { - msg_Err( p_demux, "Can't add the video device as input" ); - goto error; + fmt.video.i_sar_num = (int64_t)encoded_size.height * par_size.width / encoded_size.width; + fmt.video.i_sar_den = encoded_size.width; } - ret = [session addOutput:output error:nil /* FIXME */]; + msg_Dbg(p_demux, "encoded_size %i %i", (int)encoded_size.width, (int)encoded_size.height ); + msg_Dbg(p_demux, "display_size %i %i", (int)display_size.width, (int)display_size.height ); + msg_Dbg(p_demux, "PAR size %i %i", (int)par_size.width, (int)par_size.height ); + + [p_sys->output setPixelBufferAttributes: [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithUnsignedInt:kCVPixelFormatType_422YpCbCr8], (id)kCVPixelBufferPixelFormatTypeKey, + [NSNumber numberWithInt: p_sys->height], kCVPixelBufferHeightKey, + [NSNumber numberWithInt: p_sys->width], kCVPixelBufferWidthKey, + [NSNumber numberWithBool:YES], (id)kCVPixelBufferOpenGLCompatibilityKey, + nil]]; + + p_sys->session = [[QTCaptureSession alloc] init]; + + bool ret = [p_sys->session addInput:input error: &o_returnedError]; if( !ret ) { - msg_Err( p_demux, "Can't get any output output" ); + msg_Err( p_demux, "default video capture device could not be added to capture session (%ld)", [o_returnedError code] ); goto error; } - [session startRunning]; - - int qtchroma = [[[device formatDescriptions] objectAtIndex: 0] formatType]; /* FIXME */ - int chroma = qtchroma_to_fourcc( qtchroma ); - if( !chroma ) + ret = [p_sys->session addOutput:p_sys->output error: &o_returnedError]; + if( !ret ) { - msg_Err( p_demux, "Unknown qt chroma %4.4s provided by camera", (char*)&qtchroma ); + msg_Err( p_demux, "output could not be added to capture session (%ld)", [o_returnedError code] ); goto error; } - es_format_Init( &fmt, VIDEO_ES, chroma ); - - NSSize size = [[device attributeForKey:QTFormatDescriptionVideoEncodedPixelsSizeAttribute] sizeValue]; - p_sys->width = fmt.video.i_width = 640;/* size.width; FIXME */ - p_sys->height = fmt.video.i_height = 480;/* size.height; FIXME */ + [p_sys->session startRunning]; msg_Dbg( p_demux, "added new video es %4.4s %dx%d", (char*)&fmt.i_codec, fmt.video.i_width, fmt.video.i_height ); p_sys->p_es_video = es_out_Add( p_demux->out, &fmt ); - p_sys->output = [output retain]; - p_sys->session = [session retain]; - [input release]; - [output release]; - [session release]; [pool release]; msg_Dbg( p_demux, "QTCapture: We have a video device ready!" ); @@ -270,9 +378,6 @@ static int Open( vlc_object_t *p_this ) return VLC_SUCCESS; error: [input release]; - [session release]; - [input release]; - [output release]; [pool release]; free( p_sys ); @@ -289,8 +394,18 @@ static void Close( vlc_object_t *p_this ) demux_t *p_demux = (demux_t*)p_this; demux_sys_t *p_sys = p_demux->p_sys; - [p_sys->output release]; - [p_sys->session release]; + + /* 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)) + { + // Perform this on main thread, as the framework itself will sometimes try to synchronously + // work on main thread. And this will create a dead lock. + [p_sys->session performSelectorOnMainThread:@selector(stopRunning) withObject:nil waitUntilDone:NO]; + [p_sys->output performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO]; + [p_sys->session performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO]; + } free( p_sys ); [pool release]; @@ -315,16 +430,20 @@ static int Demux( demux_t *p_demux ) NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; - if( ![p_sys->output copyCurrentFrameToBuffer: p_block->p_buffer] ) + @synchronized (p_sys->output) + { + p_block->i_pts = [p_sys->output copyCurrentFrameToBuffer: p_block->p_buffer]; + } + + if( !p_block->i_pts ) { /* Nothing to display yet, just forget */ block_Release( p_block ); [pool release]; + msleep( 10000 ); return 1; } - p_block->i_pts = mdate(); /* FIXME */ - 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 ); @@ -356,12 +475,6 @@ static int Control( demux_t *p_demux, int i_query, va_list args ) *pi64 = (int64_t)DEFAULT_PTS_DELAY; return VLC_SUCCESS; - case DEMUX_GET_TIME: - pi64 = (int64_t*)va_arg( args, int64_t * ); - *pi64 = mdate(); - return VLC_SUCCESS; - - /* TODO implement others */ default: return VLC_EGENERIC; }