]> git.sesse.net Git - vlc/blob - plugins/macosx/vout_macosx.c
* ./plugins/lirc/lirc.c: non-blocking patch from Sigmund Augdal
[vlc] / plugins / macosx / vout_macosx.c
1 /*****************************************************************************
2  * vout_macosx.c: MacOS X video output plugin
3  *****************************************************************************
4  * Copyright (C) 2001 VideoLAN
5  *
6  * Authors: Colin Delacroix <colin@zoy.org>
7  *          Florian G. Pflug <fgp@phlo.org>
8  *          Jon Lech Johansen <jon-vl@nanocrew.net>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  * 
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #include <errno.h>                                                 /* ENOMEM */
29 #include <stdlib.h>                                                /* free() */
30 #include <string.h>                                            /* strerror() */
31
32 #include <videolan/vlc.h>
33
34 #include "video.h"
35 #include "video_output.h"
36
37 #include "interface.h"
38
39 #include "macosx.h"
40
41 #define QT_MAX_DIRECTBUFFERS 10
42
43 typedef struct picture_sys_s
44 {
45     void *p_info;
46     unsigned int i_size;
47
48     /* When using I420 output */
49     PlanarPixmapInfoYUV420 pixmap_i420;
50
51 } picture_sys_t;
52
53 /*****************************************************************************
54  * Local prototypes
55  *****************************************************************************/
56 static int  vout_Create    ( struct vout_thread_s * );
57 static int  vout_Init      ( struct vout_thread_s * );
58 static void vout_End       ( struct vout_thread_s * );
59 static void vout_Destroy   ( struct vout_thread_s * );
60 static int  vout_Manage    ( struct vout_thread_s * );
61 static void vout_Render    ( struct vout_thread_s *, struct picture_s * );
62 static void vout_Display   ( struct vout_thread_s *, struct picture_s * );
63
64 static int  CoSendRequest      ( struct vout_thread_s *, long i_request );
65 static int  CoCreateWindow     ( struct vout_thread_s * );
66 static int  CoDestroyWindow    ( struct vout_thread_s * );
67 static int  CoToggleFullscreen ( struct vout_thread_s * );
68
69 static void QTScaleMatrix      ( struct vout_thread_s * );
70 static int  QTCreateSequence   ( struct vout_thread_s * );
71 static void QTDestroySequence  ( struct vout_thread_s * );
72 static int  QTNewPicture       ( struct vout_thread_s *, struct picture_s * );
73 static void QTFreePicture      ( struct vout_thread_s *, struct picture_s * );
74
75 /*****************************************************************************
76  * Functions exported as capabilities. They are declared as static so that
77  * we don't pollute the namespace too much.
78  *****************************************************************************/
79 void _M( vout_getfunctions )( function_list_t * p_function_list )
80 {
81     p_function_list->functions.vout.pf_create     = vout_Create;
82     p_function_list->functions.vout.pf_init       = vout_Init;
83     p_function_list->functions.vout.pf_end        = vout_End;
84     p_function_list->functions.vout.pf_destroy    = vout_Destroy;
85     p_function_list->functions.vout.pf_manage     = vout_Manage;
86     p_function_list->functions.vout.pf_render     = vout_Render;
87     p_function_list->functions.vout.pf_display    = vout_Display;
88 }
89
90 /*****************************************************************************
91  * vout_Create: allocates MacOS X video thread output method
92  *****************************************************************************
93  * This function allocates and initializes a MacOS X vout method.
94  *****************************************************************************/
95 static int vout_Create( vout_thread_t *p_vout )
96 {
97     OSErr err;
98
99     if( !p_main->p_intf || !p_main->p_intf->p_module ||
100         strcmp( p_main->p_intf->p_module->psz_name, MODULE_STRING ) != 0 )
101     {
102         intf_ErrMsg( "vout error: MacOS X interface module required" );
103         return( 1 );
104     }
105
106     p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
107     if( p_vout->p_sys == NULL )
108     {
109         intf_ErrMsg( "vout error: %s", strerror( ENOMEM ) );
110         return( 1 );
111     }
112
113     memset( p_vout->p_sys, 0, sizeof( vout_sys_t ) );
114
115     p_vout->p_sys->h_img_descr = 
116         (ImageDescriptionHandle)NewHandleClear( sizeof(ImageDescription) );
117     p_vout->p_sys->p_matrix = (MatrixRecordPtr)malloc( sizeof(MatrixRecord) );
118
119     p_vout->p_sys->b_mouse_pointer_visible = 1;
120
121     p_vout->p_sys->s_rect.size.width = p_vout->render.i_width;
122     p_vout->p_sys->s_rect.size.height = p_vout->render.i_height;
123
124     if( ( err = EnterMovies() ) != noErr )
125     {
126         intf_ErrMsg( "vout error: EnterMovies failed: %d", err );
127         free( p_vout->p_sys->p_matrix );
128         DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
129         free( p_vout->p_sys );
130         return( 1 );
131     } 
132
133     if( vout_ChromaCmp( p_vout->render.i_chroma, FOURCC_I420 ) )
134     {
135         err = FindCodec( kYUV420CodecType, bestSpeedCodec,
136                          nil, &p_vout->p_sys->img_dc );
137         if( err == noErr && p_vout->p_sys->img_dc != 0 )
138         {
139             p_vout->output.i_chroma = FOURCC_I420;
140             p_vout->p_sys->i_codec = kYUV420CodecType;
141         }
142         else
143         {
144             intf_ErrMsg( "vout error: failed to find an appropriate codec" );
145         }
146     }
147     else
148     {
149         intf_ErrMsg( "vout error: chroma 0x%08x not supported",
150                      p_vout->render.i_chroma );
151     }
152
153     if( p_vout->p_sys->img_dc == 0 )
154     {
155         free( p_vout->p_sys->p_matrix );
156         DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
157         free( p_vout->p_sys );
158         return( 1 );        
159     }
160
161     if( CoCreateWindow( p_vout ) )
162     {
163         intf_ErrMsg( "vout error: unable to create window" );
164         free( p_vout->p_sys->p_matrix );
165         DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
166         free( p_vout->p_sys ); 
167         return( 1 );
168     }
169
170     return( 0 );
171 }
172
173 /*****************************************************************************
174  * vout_Init: initialize video thread output method
175  *****************************************************************************/
176 static int vout_Init( vout_thread_t *p_vout )
177 {
178     int i_index;
179     picture_t *p_pic;
180
181     I_OUTPUTPICTURES = 0;
182
183     /* Initialize the output structure; we already found a codec,
184      * and the corresponding chroma we will be using. Since we can
185      * arbitrary scale, stick to the coordinates and aspect. */
186     p_vout->output.i_width  = p_vout->render.i_width;
187     p_vout->output.i_height = p_vout->render.i_height;
188     p_vout->output.i_aspect = p_vout->render.i_aspect;
189
190     SetPort( p_vout->p_sys->p_qdport );
191     QTScaleMatrix( p_vout );
192
193     if( QTCreateSequence( p_vout ) )
194     {
195         intf_ErrMsg( "vout error: unable to create sequence" );
196         return( 1 );
197     }
198
199     /* Try to initialize up to QT_MAX_DIRECTBUFFERS direct buffers */
200     while( I_OUTPUTPICTURES < QT_MAX_DIRECTBUFFERS )
201     {
202         p_pic = NULL;
203
204         /* Find an empty picture slot */
205         for( i_index = 0; i_index < VOUT_MAX_PICTURES; i_index++ )
206         {
207             if( p_vout->p_picture[ i_index ].i_status == FREE_PICTURE )
208             {
209                 p_pic = p_vout->p_picture + i_index;
210                 break;
211             }
212         }
213
214         /* Allocate the picture */
215         if( p_pic == NULL || QTNewPicture( p_vout, p_pic ) )
216         {
217             break;
218         }
219
220         p_pic->i_status = DESTROYED_PICTURE;
221         p_pic->i_type   = DIRECT_PICTURE;
222
223         PP_OUTPUTPICTURE[ I_OUTPUTPICTURES ] = p_pic;
224
225         I_OUTPUTPICTURES++;
226     }
227
228     return( 0 );
229 }
230
231 /*****************************************************************************
232  * vout_End: terminate video thread output method
233  *****************************************************************************/
234 static void vout_End( vout_thread_t *p_vout )
235 {
236     int i_index;
237
238     QTDestroySequence( p_vout );
239
240     /* Free the direct buffers we allocated */
241     for( i_index = I_OUTPUTPICTURES; i_index; )
242     {
243         i_index--;
244         QTFreePicture( p_vout, PP_OUTPUTPICTURE[ i_index ] );
245     }
246 }
247
248 /*****************************************************************************
249  * vout_Destroy: destroy video thread output method
250  *****************************************************************************/
251 static void vout_Destroy( vout_thread_t *p_vout )
252 {
253     if( CoDestroyWindow( p_vout ) )
254     {
255         intf_ErrMsg( "vout error: unable to destroy window" );
256     }
257
258     ExitMovies();
259
260     free( p_vout->p_sys->p_matrix );
261     DisposeHandle( (Handle)p_vout->p_sys->h_img_descr );
262     free( p_vout->p_sys );
263 }
264
265 /*****************************************************************************
266  * vout_Manage: handle events
267  *****************************************************************************
268  * This function should be called regularly by video output thread. It manages
269  * console events. It returns a non null value on error.
270  *****************************************************************************/
271 static int vout_Manage( vout_thread_t *p_vout )
272 {    
273     if( p_vout->i_changes & VOUT_SIZE_CHANGE ) 
274     {
275         QTScaleMatrix( p_vout );
276         SetDSequenceMatrix( p_vout->p_sys->i_seq, 
277                             p_vout->p_sys->p_matrix );
278  
279         p_vout->i_changes &= ~VOUT_SIZE_CHANGE;
280     }
281
282     if( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE )
283     {
284         if( CoToggleFullscreen( p_vout ) )  
285         {
286             return( 1 );
287         }
288
289         p_vout->i_changes &= ~VOUT_FULLSCREEN_CHANGE;
290     }
291
292     /* hide/show mouse cursor */
293     if( p_vout->p_sys->b_mouse_moved ||
294         p_vout->p_sys->i_time_mouse_last_moved )
295     {
296         boolean_t b_change = 0;
297
298         if( !p_vout->p_sys->b_mouse_pointer_visible )
299         {
300             CGDisplayShowCursor( kCGDirectMainDisplay );
301             b_change = 1;
302         }
303         else if( !p_vout->p_sys->b_mouse_moved && 
304             mdate() - p_vout->p_sys->i_time_mouse_last_moved > 2000000 &&
305             p_vout->p_sys->b_mouse_pointer_visible )
306         {
307             CGDisplayHideCursor( kCGDirectMainDisplay );
308             b_change = 1;
309         }
310
311         if( b_change )
312         {
313             p_vout->p_sys->i_time_mouse_last_moved = 0;
314             p_vout->p_sys->b_mouse_moved = 0;
315             p_vout->p_sys->b_mouse_pointer_visible =
316                 !p_vout->p_sys->b_mouse_pointer_visible;
317         }
318     }
319
320     return( 0 );
321 }
322
323 /*****************************************************************************
324  * vout_Render: render previously calculated output
325  *****************************************************************************/
326 static void vout_Render( vout_thread_t *p_vout, picture_t *p_pic )
327 {
328     ;
329 }
330
331 /*****************************************************************************
332  * vout_Display: displays previously rendered output
333  *****************************************************************************
334  * This function sends the currently rendered image to the display.
335  *****************************************************************************/
336 static void vout_Display( vout_thread_t *p_vout, picture_t *p_pic )
337 {
338     OSErr err;
339     CodecFlags flags;
340
341     if( ( err = DecompressSequenceFrameS( 
342                     p_vout->p_sys->i_seq,
343                     p_pic->p_sys->p_info,
344                     p_pic->p_sys->i_size,                    
345                     codecFlagUseScreenBuffer, &flags, nil ) != noErr ) )
346     {
347         intf_ErrMsg( "DecompressSequenceFrameS failed: %d", err );
348     }
349 }
350
351 /*****************************************************************************
352  * CoSendRequest: send request to interface thread
353  *****************************************************************************
354  * Returns 0 on success, 1 otherwise
355  *****************************************************************************/
356 static int CoSendRequest( vout_thread_t *p_vout, long i_request )
357 {
358     NSArray *o_array;
359     NSPortMessage *o_msg;
360     struct vout_req_s req;
361     struct vout_req_s *p_req = &req;
362     NSAutoreleasePool *o_pool = [[NSAutoreleasePool alloc] init];
363     NSPort *recvPort = [[NSPort port] retain];
364
365     memset( &req, 0, sizeof(req) );
366     req.i_type = i_request;
367     req.p_vout = p_vout;
368
369     req.o_lock = [[NSConditionLock alloc] initWithCondition: 0];
370
371     o_array = [NSArray arrayWithObject:
372         [NSData dataWithBytes: &p_req length: sizeof(void *)]];
373     o_msg = [[NSPortMessage alloc]
374         initWithSendPort: p_main->p_intf->p_sys->o_port
375         receivePort: recvPort
376         components: o_array];
377
378     [o_msg sendBeforeDate: [NSDate distantPast]];
379     [req.o_lock lockWhenCondition: 1];
380     [req.o_lock unlock];
381
382     [o_msg release];
383     [req.o_lock release];
384
385     [recvPort release];
386     [o_pool release];
387
388     return( !req.i_result );
389 }
390
391 /*****************************************************************************
392  * CoCreateWindow: create new window 
393  *****************************************************************************
394  * Returns 0 on success, 1 otherwise
395  *****************************************************************************/
396 static int CoCreateWindow( vout_thread_t *p_vout )
397 {
398     if( CoSendRequest( p_vout, VOUT_REQ_CREATE_WINDOW ) )
399     {
400         intf_ErrMsg( "CoSendRequest (CREATE_WINDOW) failed" );
401         return( 1 );
402     }
403
404     return( 0 );
405 }
406
407 /*****************************************************************************
408  * CoDestroyWindow: destroy window 
409  *****************************************************************************
410  * Returns 0 on success, 1 otherwise
411  *****************************************************************************/
412 static int CoDestroyWindow( vout_thread_t *p_vout )
413 {
414     if( !p_vout->p_sys->b_mouse_pointer_visible )
415     {
416         CGDisplayShowCursor( kCGDirectMainDisplay );
417         p_vout->p_sys->b_mouse_pointer_visible = 1;
418     }
419
420     if( CoSendRequest( p_vout, VOUT_REQ_DESTROY_WINDOW ) )
421     {
422         intf_ErrMsg( "CoSendRequest (DESTROY_WINDOW) failed" );
423         return( 1 );
424     }
425
426     return( 0 );
427 }
428
429 /*****************************************************************************
430  * CoToggleFullscreen: toggle fullscreen 
431  *****************************************************************************
432  * Returns 0 on success, 1 otherwise
433  *****************************************************************************/
434 static int CoToggleFullscreen( vout_thread_t *p_vout )
435 {
436     QTDestroySequence( p_vout );
437
438     if( CoDestroyWindow( p_vout ) )
439     {
440         intf_ErrMsg( "vout error: unable to destroy window" );
441         return( 1 );
442     }
443     
444     p_vout->b_fullscreen = !p_vout->b_fullscreen;
445
446     if( CoCreateWindow( p_vout ) )
447     {
448         intf_ErrMsg( "vout error: unable to create window" );
449         return( 1 );
450     }
451
452     SetPort( p_vout->p_sys->p_qdport );
453     QTScaleMatrix( p_vout );
454
455     if( QTCreateSequence( p_vout ) )
456     {
457         intf_ErrMsg( "vout error: unable to create sequence" );
458         return( 1 ); 
459     } 
460
461     return( 0 );
462 }
463
464 /*****************************************************************************
465  * QTScaleMatrix: scale matrix 
466  *****************************************************************************/
467 static void QTScaleMatrix( vout_thread_t *p_vout )
468 {
469     Rect s_rect;
470     Fixed factor_x;
471     Fixed factor_y;
472
473     GetPortBounds( p_vout->p_sys->p_qdport, &s_rect );
474
475     factor_x = FixDiv( Long2Fix( s_rect.right - s_rect.left ),
476                        Long2Fix( p_vout->output.i_width ) );
477     factor_y = FixDiv( Long2Fix( s_rect.bottom - s_rect.top ),
478                        Long2Fix( p_vout->output.i_height ) );
479
480     SetIdentityMatrix( p_vout->p_sys->p_matrix );
481     ScaleMatrix( p_vout->p_sys->p_matrix,
482                  factor_x, factor_y,
483                  Long2Fix(0), Long2Fix(0) );            
484 }
485
486 /*****************************************************************************
487  * QTCreateSequence: create a new sequence 
488  *****************************************************************************
489  * Returns 0 on success, 1 otherwise
490  *****************************************************************************/
491 static int QTCreateSequence( vout_thread_t *p_vout )
492 {
493     OSErr err;
494     ImageDescriptionPtr p_descr;
495
496     HLock( (Handle)p_vout->p_sys->h_img_descr );
497     p_descr = *p_vout->p_sys->h_img_descr;
498
499     p_descr->idSize = sizeof(ImageDescription);
500     p_descr->cType = p_vout->p_sys->i_codec;
501     p_descr->version = 1;
502     p_descr->revisionLevel = 0;
503     p_descr->vendor = 'appl';
504     p_descr->width = p_vout->output.i_width;
505     p_descr->height = p_vout->output.i_height;
506     p_descr->hRes = Long2Fix(72);
507     p_descr->vRes = Long2Fix(72);
508     p_descr->spatialQuality = codecLosslessQuality;
509     p_descr->frameCount = 1;
510     p_descr->clutID = -1;
511
512     p_descr->dataSize = p_vout->output.i_width *
513                         p_vout->output.i_height * 3 / 2;
514     p_descr->depth = 12;
515
516     HUnlock( (Handle)p_vout->p_sys->h_img_descr );
517
518     if( ( err = DecompressSequenceBeginS( 
519                               &p_vout->p_sys->i_seq,
520                               p_vout->p_sys->h_img_descr,
521                               NULL, 0,
522                               p_vout->p_sys->p_qdport,
523                               NULL, NULL,
524                               p_vout->p_sys->p_matrix,
525                               0, NULL,
526                               codecFlagUseScreenBuffer,
527                               codecLosslessQuality,
528                               p_vout->p_sys->img_dc ) ) )
529     {
530         intf_ErrMsg( "DecompressSequenceBeginS failed: %d", err );
531         return( 1 );
532     }
533
534     return( 0 );
535 }
536
537 /*****************************************************************************
538  * QTDestroySequence: destroy sequence 
539  *****************************************************************************/
540 static void QTDestroySequence( vout_thread_t *p_vout )
541 {
542     CDSequenceEnd( p_vout->p_sys->i_seq );
543 }
544
545 /*****************************************************************************
546  * QTNewPicture: allocate a picture
547  *****************************************************************************
548  * Returns 0 on success, 1 otherwise
549  *****************************************************************************/
550 static int QTNewPicture( vout_thread_t *p_vout, picture_t *p_pic )
551 {
552     int i_width  = p_vout->output.i_width;
553     int i_height = p_vout->output.i_height;
554
555     /* We know the chroma, allocate a buffer which will be used
556      * directly by the decoder */
557     p_pic->p_sys = malloc( sizeof( picture_sys_t ) );
558
559     if( p_pic->p_sys == NULL )
560     {
561         return( -1 );
562     }
563
564     switch( p_vout->output.i_chroma )
565     {
566         case FOURCC_I420:
567
568             p_pic->p_sys->p_info = (void *)&p_pic->p_sys->pixmap_i420;
569             p_pic->p_sys->i_size = sizeof(PlanarPixmapInfoYUV420);
570
571             /* Y buffer */
572             p_pic->Y_PIXELS = memalign( 16, i_width * i_height * 3 / 2 );
573             p_pic->p[Y_PLANE].i_lines = i_height;
574             p_pic->p[Y_PLANE].i_pitch = i_width;
575             p_pic->p[Y_PLANE].i_pixel_bytes = 1;
576             p_pic->p[Y_PLANE].b_margin = 0;
577
578             /* U buffer */
579             p_pic->U_PIXELS = p_pic->Y_PIXELS + i_height * i_width;
580             p_pic->p[U_PLANE].i_lines = i_height / 2;
581             p_pic->p[U_PLANE].i_pitch = i_width / 2;
582             p_pic->p[U_PLANE].i_pixel_bytes = 1;
583             p_pic->p[U_PLANE].b_margin = 0;
584
585             /* V buffer */
586             p_pic->V_PIXELS = p_pic->U_PIXELS + i_height * i_width / 4;
587             p_pic->p[V_PLANE].i_lines = i_height / 2;
588             p_pic->p[V_PLANE].i_pitch = i_width / 2;
589             p_pic->p[V_PLANE].i_pixel_bytes = 1;
590             p_pic->p[V_PLANE].b_margin = 0;
591
592             /* We allocated 3 planes */
593             p_pic->i_planes = 3;
594
595 #define P p_pic->p_sys->pixmap_i420
596             P.componentInfoY.offset = (void *)p_pic->Y_PIXELS
597                                        - p_pic->p_sys->p_info;
598             P.componentInfoCb.offset = (void *)p_pic->U_PIXELS
599                                         - p_pic->p_sys->p_info;
600             P.componentInfoCr.offset = (void *)p_pic->V_PIXELS
601                                         - p_pic->p_sys->p_info;
602
603             P.componentInfoY.rowBytes = i_width;
604             P.componentInfoCb.rowBytes = i_width / 2;
605             P.componentInfoCr.rowBytes = i_width / 2;
606 #undef P
607
608             break;
609
610     default:
611         /* Unknown chroma, tell the guy to get lost */
612         free( p_pic->p_sys );
613         intf_ErrMsg( "vout error: never heard of chroma 0x%.8x (%4.4s)",
614                      p_vout->output.i_chroma, 
615                      (char*)&p_vout->output.i_chroma );
616         p_pic->i_planes = 0;
617         return( -1 );
618     }
619
620     return( 0 );
621 }
622
623 /*****************************************************************************
624  * QTFreePicture: destroy a picture allocated with QTNewPicture
625  *****************************************************************************/
626 static void QTFreePicture( vout_thread_t *p_vout, picture_t *p_pic )
627 {
628     free( p_pic->p_sys );
629 }
630