1 /*****************************************************************************
2 * vout.m: MacOS X video output module
3 *****************************************************************************
4 * Copyright (C) 2001-2003 VideoLAN
5 * $Id: vout.m 8351 2004-08-02 13:06:38Z hartman $
7 * Authors: Colin Delacroix <colin@zoy.org>
8 * Florian G. Pflug <fgp@phlo.org>
9 * Jon Lech Johansen <jon-vl@nanocrew.net>
10 * Derk-Jan Hartman <hartman at videolan dot org>
11 * Eric Petit <titer@m0k.org>
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
26 *****************************************************************************/
28 /*****************************************************************************
30 *****************************************************************************/
31 #include <errno.h> /* ENOMEM */
32 #include <stdlib.h> /* free() */
33 #include <string.h> /* strerror() */
35 #include <QuickTime/QuickTime.h>
42 #define QT_MAX_DIRECTBUFFERS 10
43 #define VL_MAX_DISPLAYS 16
45 /*****************************************************************************
47 *****************************************************************************/
48 @interface VLCQTView : NSQuickDrawView
50 vout_thread_t * p_vout;
53 - (id) initWithVout:(vout_thread_t *)p_vout;
59 NSAutoreleasePool *o_pool;
63 vlc_bool_t b_saved_frame;
69 MatrixRecordPtr p_matrix;
70 DecompressorComponent img_dc;
71 ImageDescriptionHandle h_img_descr;
80 /*****************************************************************************
82 *****************************************************************************/
84 static int InitVideo ( vout_thread_t * );
85 static void EndVideo ( vout_thread_t * );
86 static int ManageVideo ( vout_thread_t * );
87 static void DisplayVideo ( vout_thread_t *, picture_t * );
88 static int ControlVideo ( vout_thread_t *, int, va_list );
90 static int CoToggleFullscreen( vout_thread_t *p_vout );
91 static void QTScaleMatrix ( vout_thread_t * );
92 static int QTCreateSequence ( vout_thread_t * );
93 static void QTDestroySequence ( vout_thread_t * );
94 static int QTNewPicture ( vout_thread_t *, picture_t * );
95 static void QTFreePicture ( vout_thread_t *, picture_t * );
97 /*****************************************************************************
98 * OpenVideo: allocates MacOS X video thread output method
99 *****************************************************************************
100 * This function allocates and initializes a MacOS X vout method.
101 *****************************************************************************/
102 int E_(OpenVideoQT) ( vlc_object_t *p_this )
104 vout_thread_t * p_vout = (vout_thread_t *)p_this;
109 p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
110 if( p_vout->p_sys == NULL )
112 msg_Err( p_vout, "out of memory" );
116 memset( p_vout->p_sys, 0, sizeof( vout_sys_t ) );
118 /* Wait for a MacOS X interface to appear. Timeout is 2 seconds. */
119 for( i_timeout = 20 ; i_timeout-- ; )
123 msleep( INTF_IDLE_SLEEP );
129 /* no MacOS X intf, unable to communicate with MT */
130 msg_Err( p_vout, "no MacOS X interface present" );
131 free( p_vout->p_sys );
135 p_vout->pf_init = InitVideo;
136 p_vout->pf_end = EndVideo;
137 p_vout->pf_manage = ManageVideo;
138 p_vout->pf_render = NULL;
139 p_vout->pf_display = DisplayVideo;
140 p_vout->pf_control = ControlVideo;
142 p_vout->p_sys->o_pool = [[NSAutoreleasePool alloc] init];
144 var_Create( p_vout, "macosx-vout", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
145 var_Create( p_vout, "macosx-vdev", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
146 var_Create( p_vout, "macosx-fill", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
147 var_Create( p_vout, "macosx-stretch", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
148 var_Create( p_vout, "macosx-opaqueness", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT );
149 var_Create( p_vout, "macosx-opengl-effect", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
151 /* Initialize QuickTime */
152 p_vout->p_sys->h_img_descr =
153 (ImageDescriptionHandle)NewHandleClear( sizeof(ImageDescription) );
154 p_vout->p_sys->p_matrix =
155 (MatrixRecordPtr)malloc( sizeof(MatrixRecord) );
157 if( ( err = EnterMovies() ) != noErr )
159 msg_Err( p_vout, "EnterMovies failed: %d", err );
160 free( p_vout->p_sys->p_matrix );
161 DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
162 free( p_vout->p_sys );
166 /* Damn QT isn't thread safe. so keep a lock in the p_vlc object */
167 vlc_mutex_lock( &p_vout->p_vlc->quicktime_lock );
169 /* Can we find the right chroma ? */
170 err = FindCodec( kComponentVideoUnsigned, bestSpeedCodec,
171 nil, &p_vout->p_sys->img_dc );
173 vlc_mutex_unlock( &p_vout->p_vlc->quicktime_lock );
175 if( err == noErr && p_vout->p_sys->img_dc != 0 )
177 p_vout->output.i_chroma = VLC_FOURCC('Y','U','Y','2');
178 p_vout->p_sys->i_codec = kComponentVideoUnsigned;
182 msg_Err( p_vout, "failed to find an appropriate codec" );
185 if( p_vout->p_sys->img_dc == 0 )
187 free( p_vout->p_sys->p_matrix );
188 DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
189 free( p_vout->p_sys );
193 /* Setup the menuitem for the multiple displays. Read the vlc preference (macosx-vdev) for the primary display */
194 NSArray * o_screens = [NSScreen screens];
195 if( [o_screens count] > 0 && var_Type( p_vout, "video-device" ) == 0 )
198 vlc_value_t val2, text;
201 var_Get( p_vout, "macosx-vdev", &val );
203 var_Create( p_vout, "video-device", VLC_VAR_INTEGER |
205 text.psz_string = _("Video device");
206 var_Change( p_vout, "video-device", VLC_VAR_SETTEXT, &text, NULL );
208 NSEnumerator * o_enumerator = [o_screens objectEnumerator];
210 while( (o_screen = [o_enumerator nextObject]) != NULL )
213 NSRect s_rect = [o_screen frame];
215 snprintf( psz_temp, sizeof(psz_temp)/sizeof(psz_temp[0])-1,
216 "%s %d (%dx%d)", _("Screen"), i,
217 (int)s_rect.size.width, (int)s_rect.size.height );
219 text.psz_string = psz_temp;
221 var_Change( p_vout, "video-device",
222 VLC_VAR_ADDCHOICE, &val2, &text );
224 if( ( i - 1 ) == val.i_int )
226 var_Set( p_vout, "video-device", val2 );
231 var_AddCallback( p_vout, "video-device", vout_VarCallback,
234 val2.b_bool = VLC_TRUE;
235 var_Set( p_vout, "intf-change", val2 );
239 p_vout->p_sys->o_window =
240 [[VLCWindow alloc] initWithVout: p_vout frame: nil];
242 #define o_qtview p_vout->p_sys->o_qtview
243 o_qtview = [[VLCQTView alloc] initWithVout: p_vout];
244 [p_vout->p_sys->o_window setContentView: o_qtview];
245 [o_qtview autorelease];
247 /* Retrieve the QuickDraw port */
248 [o_qtview lockFocus];
249 p_vout->p_sys->p_qdport = [o_qtview qdPort];
250 [o_qtview unlockFocus];
256 /*****************************************************************************
257 * CloseVideo: destroy video thread output method
258 *****************************************************************************/
259 void E_(CloseVideoQT) ( vlc_object_t *p_this )
261 NSAutoreleasePool *o_pool = [[NSAutoreleasePool alloc] init];
262 vout_thread_t * p_vout = (vout_thread_t *)p_this;
264 [p_vout->p_sys->o_window close];
265 [p_vout->p_sys->o_window release];
267 /* Clean Up Quicktime environment */
269 free( p_vout->p_sys->p_matrix );
270 DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
273 free( p_vout->p_sys );
276 /*****************************************************************************
277 * InitVideo: initialize video thread output method
278 *****************************************************************************/
279 static int InitVideo ( vout_thread_t *p_vout )
284 I_OUTPUTPICTURES = 0;
286 /* Initialize the output structure; we already found a codec,
287 * and the corresponding chroma we will be using. Since we can
288 * arbitrary scale, stick to the coordinates and aspect. */
289 p_vout->output.i_width = p_vout->render.i_width;
290 p_vout->output.i_height = p_vout->render.i_height;
291 p_vout->output.i_aspect = p_vout->render.i_aspect;
293 SetPort( p_vout->p_sys->p_qdport );
294 QTScaleMatrix( p_vout );
296 if( QTCreateSequence( p_vout ) )
298 msg_Err( p_vout, "unable to create sequence" );
302 /* Try to initialize up to QT_MAX_DIRECTBUFFERS direct buffers */
303 while( I_OUTPUTPICTURES < QT_MAX_DIRECTBUFFERS )
307 /* Find an empty picture slot */
308 for( i_index = 0; i_index < VOUT_MAX_PICTURES; i_index++ )
310 if( p_vout->p_picture[ i_index ].i_status == FREE_PICTURE )
312 p_pic = p_vout->p_picture + i_index;
317 /* Allocate the picture */
318 if( p_pic == NULL || QTNewPicture( p_vout, p_pic ) )
323 p_pic->i_status = DESTROYED_PICTURE;
324 p_pic->i_type = DIRECT_PICTURE;
326 PP_OUTPUTPICTURE[ I_OUTPUTPICTURES ] = p_pic;
332 /*****************************************************************************
333 * EndVideo: terminate video thread output method
334 *****************************************************************************/
335 static void EndVideo( vout_thread_t *p_vout )
339 QTDestroySequence( p_vout );
341 /* Free the direct buffers we allocated */
342 for( i_index = I_OUTPUTPICTURES; i_index; )
345 QTFreePicture( p_vout, PP_OUTPUTPICTURE[ i_index ] );
349 /*****************************************************************************
350 * ManageVideo: handle events
351 *****************************************************************************
352 * This function should be called regularly by video output thread. It manages
353 * console events. It returns a non null value on error.
354 *****************************************************************************/
355 static int ManageVideo( vout_thread_t *p_vout )
357 if( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE )
359 if( CoToggleFullscreen( p_vout ) )
364 p_vout->i_changes &= ~VOUT_FULLSCREEN_CHANGE;
367 if( p_vout->i_changes & VOUT_SIZE_CHANGE )
369 QTScaleMatrix( p_vout );
370 SetDSequenceMatrix( p_vout->p_sys->i_seq,
371 p_vout->p_sys->p_matrix );
373 p_vout->i_changes &= ~VOUT_SIZE_CHANGE;
376 [p_vout->p_sys->o_window manage];
381 /*****************************************************************************
382 * vout_Display: displays previously rendered output
383 *****************************************************************************
384 * This function sends the currently rendered image to the display.
385 *****************************************************************************/
386 static void DisplayVideo( vout_thread_t *p_vout, picture_t *p_pic )
391 if( ( err = DecompressSequenceFrameWhen(
392 p_vout->p_sys->i_seq,
393 p_pic->p_sys->p_data,
394 p_pic->p_sys->i_size,
395 codecFlagUseImageBuffer, &flags, NULL, NULL ) != noErr ) )
397 msg_Warn( p_vout, "DecompressSequenceFrameWhen failed: %d", err );
401 QDFlushPortBuffer( p_vout->p_sys->p_qdport, nil );
405 /*****************************************************************************
406 * ControlVideo: control facility for the vout
407 *****************************************************************************/
408 static int ControlVideo( vout_thread_t *p_vout, int i_query, va_list args )
414 case VOUT_SET_STAY_ON_TOP:
415 b_arg = va_arg( args, vlc_bool_t );
416 [p_vout->p_sys->o_window setOnTop: b_arg];
422 return vout_vaControlDefault( p_vout, i_query, args );
426 /*****************************************************************************
427 * CoToggleFullscreen: toggle fullscreen
428 *****************************************************************************
429 * Returns 0 on success, 1 otherwise
430 *****************************************************************************/
431 static int CoToggleFullscreen( vout_thread_t *p_vout )
433 NSAutoreleasePool *o_pool = [[NSAutoreleasePool alloc] init];
435 QTDestroySequence( p_vout );
437 if( !p_vout->b_fullscreen )
439 /* Save window size and position */
440 p_vout->p_sys->s_frame.size =
441 [[p_vout->p_sys->o_window contentView] frame].size;
442 p_vout->p_sys->s_frame.origin =
443 [p_vout->p_sys->o_window frame].origin;
444 p_vout->p_sys->b_saved_frame = VLC_TRUE;
446 [p_vout->p_sys->o_window close];
448 p_vout->b_fullscreen = !p_vout->b_fullscreen;
450 if( p_vout->p_sys->b_saved_frame )
452 p_vout->p_sys->o_window = [[VLCWindow alloc]
453 initWithVout: p_vout frame: &p_vout->p_sys->s_frame];
457 p_vout->p_sys->o_window = [[VLCWindow alloc]
458 initWithVout: p_vout frame: nil];
461 #define o_qtview p_vout->p_sys->o_qtview
462 o_qtview = [[VLCQTView alloc] initWithVout: p_vout];
463 [p_vout->p_sys->o_window setContentView: o_qtview];
464 [o_qtview autorelease];
466 /* Retrieve the QuickDraw port */
467 [o_qtview lockFocus];
468 p_vout->p_sys->p_qdport = [o_qtview qdPort];
469 [o_qtview unlockFocus];
472 SetPort( p_vout->p_sys->p_qdport );
473 QTScaleMatrix( p_vout );
475 if( QTCreateSequence( p_vout ) )
477 msg_Err( p_vout, "unable to create sequence" );
485 /*****************************************************************************
486 * QTScaleMatrix: scale matrix
487 *****************************************************************************/
488 static void QTScaleMatrix( vout_thread_t *p_vout )
492 unsigned int i_width, i_height;
493 Fixed factor_x, factor_y;
494 unsigned int i_offset_x = 0;
495 unsigned int i_offset_y = 0;
497 GetPortBounds( p_vout->p_sys->p_qdport, &s_rect );
499 i_width = s_rect.right - s_rect.left;
500 i_height = s_rect.bottom - s_rect.top;
502 var_Get( p_vout, "macosx-stretch", &val );
505 factor_x = FixDiv( Long2Fix( i_width ),
506 Long2Fix( p_vout->output.i_width ) );
507 factor_y = FixDiv( Long2Fix( i_height ),
508 Long2Fix( p_vout->output.i_height ) );
511 else if( i_height * p_vout->output.i_aspect < i_width * VOUT_ASPECT_FACTOR )
513 int i_adj_width = i_height * p_vout->output.i_aspect /
516 factor_x = FixDiv( Long2Fix( i_adj_width ),
517 Long2Fix( p_vout->output.i_width ) );
518 factor_y = FixDiv( Long2Fix( i_height ),
519 Long2Fix( p_vout->output.i_height ) );
521 i_offset_x = (i_width - i_adj_width) / 2;
525 int i_adj_height = i_width * VOUT_ASPECT_FACTOR /
526 p_vout->output.i_aspect;
528 factor_x = FixDiv( Long2Fix( i_width ),
529 Long2Fix( p_vout->output.i_width ) );
530 factor_y = FixDiv( Long2Fix( i_adj_height ),
531 Long2Fix( p_vout->output.i_height ) );
533 i_offset_y = (i_height - i_adj_height) / 2;
536 SetIdentityMatrix( p_vout->p_sys->p_matrix );
538 ScaleMatrix( p_vout->p_sys->p_matrix,
540 Long2Fix(0), Long2Fix(0) );
542 TranslateMatrix( p_vout->p_sys->p_matrix,
543 Long2Fix(i_offset_x), Long2Fix(i_offset_y) );
546 /*****************************************************************************
547 * QTCreateSequence: create a new sequence
548 *****************************************************************************
549 * Returns 0 on success, 1 otherwise
550 *****************************************************************************/
551 static int QTCreateSequence( vout_thread_t *p_vout )
554 ImageDescriptionPtr p_descr;
556 HLock( (Handle)p_vout->p_sys->h_img_descr );
557 p_descr = *p_vout->p_sys->h_img_descr;
559 p_descr->idSize = sizeof(ImageDescription);
560 p_descr->cType = p_vout->p_sys->i_codec;
561 p_descr->version = 2;
562 p_descr->revisionLevel = 0;
563 p_descr->vendor = 'mpla';
564 p_descr->width = p_vout->output.i_width;
565 p_descr->height = p_vout->output.i_height;
566 p_descr->hRes = Long2Fix(72);
567 p_descr->vRes = Long2Fix(72);
568 p_descr->spatialQuality = codecLosslessQuality;
569 p_descr->frameCount = 1;
570 p_descr->clutID = -1;
571 p_descr->dataSize = 0;
574 HUnlock( (Handle)p_vout->p_sys->h_img_descr );
576 if( ( err = DecompressSequenceBeginS(
577 &p_vout->p_sys->i_seq,
578 p_vout->p_sys->h_img_descr,
580 (p_descr->width * p_descr->height * 16) / 8,
581 p_vout->p_sys->p_qdport,
583 p_vout->p_sys->p_matrix,
585 codecFlagUseImageBuffer,
586 codecLosslessQuality,
589 msg_Err( p_vout, "DecompressSequenceBeginS failed: %d", err );
596 /*****************************************************************************
597 * QTDestroySequence: destroy sequence
598 *****************************************************************************/
599 static void QTDestroySequence( vout_thread_t *p_vout )
601 CDSequenceEnd( p_vout->p_sys->i_seq );
604 /*****************************************************************************
605 * QTNewPicture: allocate a picture
606 *****************************************************************************
607 * Returns 0 on success, 1 otherwise
608 *****************************************************************************/
609 static int QTNewPicture( vout_thread_t *p_vout, picture_t *p_pic )
611 /* We know the chroma, allocate a buffer which will be used
612 * directly by the decoder */
613 p_pic->p_sys = malloc( sizeof( picture_sys_t ) );
615 if( p_pic->p_sys == NULL )
620 vout_InitPicture( VLC_OBJECT( p_vout), p_pic, p_vout->output.i_chroma,
621 p_vout->output.i_width, p_vout->output.i_height,
622 p_vout->output.i_aspect );
624 switch( p_vout->output.i_chroma )
626 case VLC_FOURCC('Y','U','Y','2'):
627 p_pic->p_sys->i_size = p_vout->output.i_width * p_vout->output.i_height * 2;
629 /* Allocate the memory buffer */
630 p_pic->p_data = vlc_memalign( &p_pic->p_data_orig,
631 16, p_pic->p_sys->i_size );
633 p_pic->p[0].p_pixels = p_pic->p_data;
634 p_pic->p[0].i_lines = p_vout->output.i_height;
635 p_pic->p[0].i_pitch = p_vout->output.i_width * 2;
636 p_pic->p[0].i_pixel_pitch = 1;
637 p_pic->p[0].i_visible_pitch = p_vout->output.i_width * 2;
640 p_pic->p_sys->p_data = (void *)p_pic->p[0].p_pixels;
645 /* Unknown chroma, tell the guy to get lost */
646 free( p_pic->p_sys );
647 msg_Err( p_vout, "never heard of chroma 0x%.8x (%4.4s)",
648 p_vout->output.i_chroma, (char*)&p_vout->output.i_chroma );
656 /*****************************************************************************
657 * QTFreePicture: destroy a picture allocated with QTNewPicture
658 *****************************************************************************/
659 static void QTFreePicture( vout_thread_t *p_vout, picture_t *p_pic )
661 switch( p_vout->output.i_chroma )
663 case VLC_FOURCC('I','4','2','0'):
664 free( p_pic->p_data_orig );
668 free( p_pic->p_sys );
671 /*****************************************************************************
672 * VLCQTView implementation
673 *****************************************************************************/
674 @implementation VLCQTView
676 - (id) initWithVout:(vout_thread_t *)_p_vout
682 - (void)drawRect:(NSRect)rect
684 [[NSColor blackColor] set];
686 [super drawRect: rect];
688 p_vout->i_changes |= VOUT_SIZE_CHANGE;