1 /*****************************************************************************
2 * vout.m: MacOS X video output module
3 *****************************************************************************
4 * Copyright (C) 2001-2004 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;
70 MatrixRecordPtr p_matrix;
71 DecompressorComponent img_dc;
72 ImageDescriptionHandle h_img_descr;
80 /* When using I420 output */
81 PlanarPixmapInfoYUV420 pixmap_i420;
84 /*****************************************************************************
86 *****************************************************************************/
88 static int InitVideo ( vout_thread_t * );
89 static void EndVideo ( vout_thread_t * );
90 static int ManageVideo ( vout_thread_t * );
91 static void DisplayVideo ( vout_thread_t *, picture_t * );
92 static int ControlVideo ( vout_thread_t *, int, va_list );
94 static int CoToggleFullscreen( vout_thread_t *p_vout );
95 static void QTScaleMatrix ( vout_thread_t * );
96 static int QTCreateSequence ( vout_thread_t * );
97 static void QTDestroySequence ( vout_thread_t * );
98 static int QTNewPicture ( vout_thread_t *, picture_t * );
99 static void QTFreePicture ( vout_thread_t *, picture_t * );
101 /*****************************************************************************
102 * OpenVideo: allocates MacOS X video thread output method
103 *****************************************************************************
104 * This function allocates and initializes a MacOS X vout method.
105 *****************************************************************************/
106 int E_(OpenVideoQT) ( vlc_object_t *p_this )
108 vout_thread_t * p_vout = (vout_thread_t *)p_this;
111 p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
112 if( p_vout->p_sys == NULL )
114 msg_Err( p_vout, "out of memory" );
118 memset( p_vout->p_sys, 0, sizeof( vout_sys_t ) );
120 p_vout->p_sys->o_pool = [[NSAutoreleasePool alloc] init];
122 p_vout->pf_init = InitVideo;
123 p_vout->pf_end = EndVideo;
124 p_vout->pf_manage = ManageVideo;
125 p_vout->pf_render = NULL;
126 p_vout->pf_display = DisplayVideo;
127 p_vout->pf_control = ControlVideo;
130 p_vout->p_sys->o_window =
131 [[VLCWindow alloc] initWithVout: p_vout frame: nil];
132 if( !p_vout->p_sys->o_window )
137 p_vout->p_sys->b_altivec = p_vout->p_libvlc->i_cpu & CPU_CAPABILITY_ALTIVEC;
138 msg_Dbg( p_vout, "We do%s have Altivec", p_vout->p_sys->b_altivec ? "" : "n't" );
140 /* Initialize QuickTime */
141 p_vout->p_sys->h_img_descr =
142 (ImageDescriptionHandle)NewHandleClear( sizeof(ImageDescription) );
143 p_vout->p_sys->p_matrix =
144 (MatrixRecordPtr)malloc( sizeof(MatrixRecord) );
146 if( ( err = EnterMovies() ) != noErr )
148 msg_Err( p_vout, "EnterMovies failed: %d", err );
149 free( p_vout->p_sys->p_matrix );
150 DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
151 free( p_vout->p_sys );
155 /* Damn QT isn't thread safe. so keep a lock in the p_vlc object */
156 vlc_mutex_lock( &p_vout->p_vlc->quicktime_lock );
158 /* Can we find the right chroma ? */
159 if( p_vout->p_sys->b_altivec )
161 err = FindCodec( kYUVSPixelFormat, bestSpeedCodec,
162 nil, &p_vout->p_sys->img_dc );
166 err = FindCodec( kYUV420CodecType, bestSpeedCodec,
167 nil, &p_vout->p_sys->img_dc );
169 vlc_mutex_unlock( &p_vout->p_vlc->quicktime_lock );
171 if( err == noErr && p_vout->p_sys->img_dc != 0 )
173 if( p_vout->p_sys->b_altivec )
175 p_vout->output.i_chroma = VLC_FOURCC('Y','U','Y','2');
176 p_vout->p_sys->i_codec = kYUVSPixelFormat;
180 p_vout->output.i_chroma = VLC_FOURCC('I','4','2','0');
181 p_vout->p_sys->i_codec = kYUV420CodecType;
186 msg_Err( p_vout, "failed to find an appropriate codec" );
189 if( p_vout->p_sys->img_dc == 0 )
191 free( p_vout->p_sys->p_matrix );
192 DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
193 free( p_vout->p_sys );
197 #define o_qtview p_vout->p_sys->o_qtview
198 o_qtview = [[VLCQTView alloc] initWithVout: p_vout];
199 [p_vout->p_sys->o_window setContentView: o_qtview];
200 [o_qtview autorelease];
202 /* Retrieve the QuickDraw port */
203 [o_qtview lockFocus];
204 p_vout->p_sys->p_qdport = [o_qtview qdPort];
205 [o_qtview unlockFocus];
211 /*****************************************************************************
212 * CloseVideo: destroy video thread output method
213 *****************************************************************************/
214 void E_(CloseVideoQT) ( vlc_object_t *p_this )
216 NSAutoreleasePool *o_pool = [[NSAutoreleasePool alloc] init];
217 vout_thread_t * p_vout = (vout_thread_t *)p_this;
219 [p_vout->p_sys->o_window close];
221 /* Clean Up Quicktime environment */
223 free( p_vout->p_sys->p_matrix );
224 DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
227 free( p_vout->p_sys );
230 /*****************************************************************************
231 * InitVideo: initialize video thread output method
232 *****************************************************************************/
233 static int InitVideo ( vout_thread_t *p_vout )
238 I_OUTPUTPICTURES = 0;
240 /* Initialize the output structure; we already found a codec,
241 * and the corresponding chroma we will be using. Since we can
242 * arbitrary scale, stick to the coordinates and aspect. */
243 p_vout->output.i_width = p_vout->render.i_width;
244 p_vout->output.i_height = p_vout->render.i_height;
245 p_vout->output.i_aspect = p_vout->render.i_aspect;
247 SetPort( p_vout->p_sys->p_qdport );
248 QTScaleMatrix( p_vout );
250 if( QTCreateSequence( p_vout ) )
252 msg_Err( p_vout, "unable to create sequence" );
256 /* Try to initialize up to QT_MAX_DIRECTBUFFERS direct buffers */
257 while( I_OUTPUTPICTURES < QT_MAX_DIRECTBUFFERS )
261 /* Find an empty picture slot */
262 for( i_index = 0; i_index < VOUT_MAX_PICTURES; i_index++ )
264 if( p_vout->p_picture[ i_index ].i_status == FREE_PICTURE )
266 p_pic = p_vout->p_picture + i_index;
271 /* Allocate the picture */
272 if( p_pic == NULL || QTNewPicture( p_vout, p_pic ) )
277 p_pic->i_status = DESTROYED_PICTURE;
278 p_pic->i_type = DIRECT_PICTURE;
280 PP_OUTPUTPICTURE[ I_OUTPUTPICTURES ] = p_pic;
286 /*****************************************************************************
287 * EndVideo: terminate video thread output method
288 *****************************************************************************/
289 static void EndVideo( vout_thread_t *p_vout )
293 QTDestroySequence( p_vout );
295 /* Free the direct buffers we allocated */
296 for( i_index = I_OUTPUTPICTURES; i_index; )
299 QTFreePicture( p_vout, PP_OUTPUTPICTURE[ i_index ] );
303 /*****************************************************************************
304 * ManageVideo: handle events
305 *****************************************************************************
306 * This function should be called regularly by video output thread. It manages
307 * console events. It returns a non null value on error.
308 *****************************************************************************/
309 static int ManageVideo( vout_thread_t *p_vout )
311 if( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE )
313 if( CoToggleFullscreen( p_vout ) )
318 p_vout->i_changes &= ~VOUT_FULLSCREEN_CHANGE;
321 if( p_vout->i_changes & VOUT_SIZE_CHANGE )
323 QTScaleMatrix( p_vout );
324 SetDSequenceMatrix( p_vout->p_sys->i_seq,
325 p_vout->p_sys->p_matrix );
327 p_vout->i_changes &= ~VOUT_SIZE_CHANGE;
330 [p_vout->p_sys->o_window manage];
335 /*****************************************************************************
336 * vout_Display: displays previously rendered output
337 *****************************************************************************
338 * This function sends the currently rendered image to the display.
339 *****************************************************************************/
340 static void DisplayVideo( vout_thread_t *p_vout, picture_t *p_pic )
345 if( ( err = DecompressSequenceFrameWhen(
346 p_vout->p_sys->i_seq,
347 p_pic->p_sys->p_data,
348 p_pic->p_sys->i_size,
349 codecFlagUseImageBuffer, &flags, NULL, NULL ) != noErr ) )
351 msg_Warn( p_vout, "DecompressSequenceFrameWhen failed: %d", err );
355 QDFlushPortBuffer( p_vout->p_sys->p_qdport, nil );
359 /*****************************************************************************
360 * ControlVideo: control facility for the vout
361 *****************************************************************************/
362 static int ControlVideo( vout_thread_t *p_vout, int i_query, va_list args )
368 case VOUT_SET_STAY_ON_TOP:
369 b_arg = va_arg( args, vlc_bool_t );
370 [p_vout->p_sys->o_window setOnTop: b_arg];
376 return vout_vaControlDefault( p_vout, i_query, args );
380 /*****************************************************************************
381 * CoToggleFullscreen: toggle fullscreen
382 *****************************************************************************
383 * Returns 0 on success, 1 otherwise
384 *****************************************************************************/
385 static int CoToggleFullscreen( vout_thread_t *p_vout )
387 NSAutoreleasePool *o_pool = [[NSAutoreleasePool alloc] init];
389 QTDestroySequence( p_vout );
391 if( !p_vout->b_fullscreen )
393 /* Save window size and position */
394 p_vout->p_sys->s_frame.size =
395 [[p_vout->p_sys->o_window contentView] frame].size;
396 p_vout->p_sys->s_frame.origin =
397 [p_vout->p_sys->o_window frame].origin;
398 p_vout->p_sys->b_saved_frame = VLC_TRUE;
400 [p_vout->p_sys->o_window close];
402 p_vout->b_fullscreen = !p_vout->b_fullscreen;
404 if( p_vout->p_sys->b_saved_frame )
406 p_vout->p_sys->o_window = [[VLCWindow alloc]
407 initWithVout: p_vout frame: &p_vout->p_sys->s_frame];
411 p_vout->p_sys->o_window = [[VLCWindow alloc]
412 initWithVout: p_vout frame: nil];
415 #define o_qtview p_vout->p_sys->o_qtview
416 o_qtview = [[VLCQTView alloc] initWithVout: p_vout];
417 [p_vout->p_sys->o_window setContentView: o_qtview];
418 [o_qtview autorelease];
420 /* Retrieve the QuickDraw port */
421 [o_qtview lockFocus];
422 p_vout->p_sys->p_qdport = [o_qtview qdPort];
423 [o_qtview unlockFocus];
426 SetPort( p_vout->p_sys->p_qdport );
427 QTScaleMatrix( p_vout );
429 if( QTCreateSequence( p_vout ) )
431 msg_Err( p_vout, "unable to create sequence" );
439 /*****************************************************************************
440 * QTScaleMatrix: scale matrix
441 *****************************************************************************/
442 static void QTScaleMatrix( vout_thread_t *p_vout )
446 unsigned int i_width, i_height;
447 Fixed factor_x, factor_y;
448 unsigned int i_offset_x = 0;
449 unsigned int i_offset_y = 0;
451 GetPortBounds( p_vout->p_sys->p_qdport, &s_rect );
453 i_width = s_rect.right - s_rect.left;
454 i_height = s_rect.bottom - s_rect.top;
456 var_Get( p_vout, "macosx-stretch", &val );
459 factor_x = FixDiv( Long2Fix( i_width ),
460 Long2Fix( p_vout->output.i_width ) );
461 factor_y = FixDiv( Long2Fix( i_height ),
462 Long2Fix( p_vout->output.i_height ) );
465 else if( i_height * p_vout->output.i_aspect < i_width * VOUT_ASPECT_FACTOR )
467 int i_adj_width = i_height * p_vout->output.i_aspect /
470 factor_x = FixDiv( Long2Fix( i_adj_width ),
471 Long2Fix( p_vout->output.i_width ) );
472 factor_y = FixDiv( Long2Fix( i_height ),
473 Long2Fix( p_vout->output.i_height ) );
475 i_offset_x = (i_width - i_adj_width) / 2;
479 int i_adj_height = i_width * VOUT_ASPECT_FACTOR /
480 p_vout->output.i_aspect;
482 factor_x = FixDiv( Long2Fix( i_width ),
483 Long2Fix( p_vout->output.i_width ) );
484 factor_y = FixDiv( Long2Fix( i_adj_height ),
485 Long2Fix( p_vout->output.i_height ) );
487 i_offset_y = (i_height - i_adj_height) / 2;
490 SetIdentityMatrix( p_vout->p_sys->p_matrix );
492 ScaleMatrix( p_vout->p_sys->p_matrix,
494 Long2Fix(0), Long2Fix(0) );
496 TranslateMatrix( p_vout->p_sys->p_matrix,
497 Long2Fix(i_offset_x), Long2Fix(i_offset_y) );
500 /*****************************************************************************
501 * QTCreateSequence: create a new sequence
502 *****************************************************************************
503 * Returns 0 on success, 1 otherwise
504 *****************************************************************************/
505 static int QTCreateSequence( vout_thread_t *p_vout )
508 ImageDescriptionPtr p_descr;
510 HLock( (Handle)p_vout->p_sys->h_img_descr );
511 p_descr = *p_vout->p_sys->h_img_descr;
513 p_descr->idSize = sizeof(ImageDescription);
514 p_descr->cType = p_vout->p_sys->i_codec;
515 p_descr->version = 2;
516 p_descr->revisionLevel = 0;
517 p_descr->vendor = 'mpla';
518 p_descr->width = p_vout->output.i_width;
519 p_descr->height = p_vout->output.i_height;
520 p_descr->hRes = Long2Fix(72);
521 p_descr->vRes = Long2Fix(72);
522 p_descr->spatialQuality = codecLosslessQuality;
523 p_descr->frameCount = 1;
524 p_descr->clutID = -1;
525 p_descr->dataSize = 0;
528 HUnlock( (Handle)p_vout->p_sys->h_img_descr );
530 if( ( err = DecompressSequenceBeginS(
531 &p_vout->p_sys->i_seq,
532 p_vout->p_sys->h_img_descr,
534 (p_descr->width * p_descr->height * 16) / 8,
535 p_vout->p_sys->p_qdport,
537 p_vout->p_sys->p_matrix,
539 codecFlagUseImageBuffer,
540 codecLosslessQuality,
543 msg_Err( p_vout, "DecompressSequenceBeginS failed: %d", err );
550 /*****************************************************************************
551 * QTDestroySequence: destroy sequence
552 *****************************************************************************/
553 static void QTDestroySequence( vout_thread_t *p_vout )
555 CDSequenceEnd( p_vout->p_sys->i_seq );
558 /*****************************************************************************
559 * QTNewPicture: allocate a picture
560 *****************************************************************************
561 * Returns 0 on success, 1 otherwise
562 *****************************************************************************/
563 static int QTNewPicture( vout_thread_t *p_vout, picture_t *p_pic )
565 /* We know the chroma, allocate a buffer which will be used
566 * directly by the decoder */
567 p_pic->p_sys = malloc( sizeof( picture_sys_t ) );
569 if( p_pic->p_sys == NULL )
574 vout_InitPicture( VLC_OBJECT( p_vout), p_pic, p_vout->output.i_chroma,
575 p_vout->output.i_width, p_vout->output.i_height,
576 p_vout->output.i_aspect );
578 switch( p_vout->output.i_chroma )
580 case VLC_FOURCC('Y','U','Y','2'):
581 p_pic->p_sys->i_size = p_vout->output.i_width * p_vout->output.i_height * 2;
583 /* Allocate the memory buffer */
584 p_pic->p_data = vlc_memalign( &p_pic->p_data_orig,
585 16, p_pic->p_sys->i_size );
587 p_pic->p[0].p_pixels = p_pic->p_data;
588 p_pic->p[0].i_lines = p_vout->output.i_height;
589 p_pic->p[0].i_visible_lines = p_vout->output.i_height;
590 p_pic->p[0].i_pitch = p_vout->output.i_width * 2;
591 p_pic->p[0].i_pixel_pitch = 1;
592 p_pic->p[0].i_visible_pitch = p_vout->output.i_width * 2;
595 p_pic->p_sys->p_data = (void *)p_pic->p[0].p_pixels;
599 case VLC_FOURCC('I','4','2','0'):
600 p_pic->p_sys->p_data = (void *)&p_pic->p_sys->pixmap_i420;
601 p_pic->p_sys->i_size = sizeof(PlanarPixmapInfoYUV420);
603 /* Allocate the memory buffer */
604 p_pic->p_data = vlc_memalign( &p_pic->p_data_orig,
605 16, p_vout->output.i_width * p_vout->output.i_height * 3 / 2 );
608 p_pic->Y_PIXELS = p_pic->p_data;
609 p_pic->p[Y_PLANE].i_lines = p_vout->output.i_height;
610 p_pic->p[Y_PLANE].i_visible_lines = p_vout->output.i_height;
611 p_pic->p[Y_PLANE].i_pitch = p_vout->output.i_width;
612 p_pic->p[Y_PLANE].i_pixel_pitch = 1;
613 p_pic->p[Y_PLANE].i_visible_pitch = p_vout->output.i_width;
616 p_pic->U_PIXELS = p_pic->Y_PIXELS + p_vout->output.i_height * p_vout->output.i_width;
617 p_pic->p[U_PLANE].i_lines = p_vout->output.i_height / 2;
618 p_pic->p[U_PLANE].i_visible_lines = p_vout->output.i_height / 2;
619 p_pic->p[U_PLANE].i_pitch = p_vout->output.i_width / 2;
620 p_pic->p[U_PLANE].i_pixel_pitch = 1;
621 p_pic->p[U_PLANE].i_visible_pitch = p_vout->output.i_width / 2;
624 p_pic->V_PIXELS = p_pic->U_PIXELS + p_vout->output.i_height * p_vout->output.i_width / 4;
625 p_pic->p[V_PLANE].i_lines = p_vout->output.i_height / 2;
626 p_pic->p[V_PLANE].i_visible_lines = p_vout->output.i_height / 2;
627 p_pic->p[V_PLANE].i_pitch = p_vout->output.i_width / 2;
628 p_pic->p[V_PLANE].i_pixel_pitch = 1;
629 p_pic->p[V_PLANE].i_visible_pitch = p_vout->output.i_width / 2;
631 /* We allocated 3 planes */
634 #define P p_pic->p_sys->pixmap_i420
635 P.componentInfoY.offset = (void *)p_pic->Y_PIXELS
636 - p_pic->p_sys->p_data;
637 P.componentInfoCb.offset = (void *)p_pic->U_PIXELS
638 - p_pic->p_sys->p_data;
639 P.componentInfoCr.offset = (void *)p_pic->V_PIXELS
640 - p_pic->p_sys->p_data;
642 P.componentInfoY.rowBytes = p_vout->output.i_width;
643 P.componentInfoCb.rowBytes = p_vout->output.i_width / 2;
644 P.componentInfoCr.rowBytes = p_vout->output.i_width / 2;
649 /* Unknown chroma, tell the guy to get lost */
650 free( p_pic->p_sys );
651 msg_Err( p_vout, "never heard of chroma 0x%.8x (%4.4s)",
652 p_vout->output.i_chroma, (char*)&p_vout->output.i_chroma );
660 /*****************************************************************************
661 * QTFreePicture: destroy a picture allocated with QTNewPicture
662 *****************************************************************************/
663 static void QTFreePicture( vout_thread_t *p_vout, picture_t *p_pic )
665 switch( p_vout->output.i_chroma )
667 case VLC_FOURCC('I','4','2','0'):
668 free( p_pic->p_data_orig );
672 free( p_pic->p_sys );
675 /*****************************************************************************
676 * VLCQTView implementation
677 *****************************************************************************/
678 @implementation VLCQTView
680 - (id) initWithVout:(vout_thread_t *)_p_vout
686 - (void)drawRect:(NSRect)rect
688 [[NSColor blackColor] set];
690 [super drawRect: rect];
692 p_vout->i_changes |= VOUT_SIZE_CHANGE;