[vlc-devel] [PATCH 3/5] Add CALayer module

David Menestrina dmenest-vlc at ofb.net
Tue May 11 00:32:03 CEST 2010


---
 configure.ac                    |   10 +
 modules/video_output/Modules.am |    1 +
 modules/video_output/calayer.m  |  589 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 600 insertions(+), 0 deletions(-)
 create mode 100644 modules/video_output/calayer.m

diff --git a/configure.ac b/configure.ac
index 9cf52e9..2b2d2d2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4070,6 +4070,16 @@ then
   VLC_ADD_PLUGIN([vout_macosx])
 fi
 
+AC_ARG_ENABLE(macosx-calayer,
+  [  --enable-macosx-calayer    Mac OS X CALayer video output module (default enabled on Mac OS X)])
+if test "x${enable_macosx_calayer}" != "xno" &&
+  (test "${SYS}" = "darwin" || test "${enable_macosx_calayer}" = "yes")
+then
+  VLC_ADD_LDFLAGS([vout_calayer],[-Wl,-framework,Cocoa])
+  VLC_ADD_LDFLAGS([vout_calayer],[-Wl,-framework,QuartzCore])
+  VLC_ADD_PLUGIN([vout_calayer])
+fi
+
 AC_ARG_ENABLE(macosx-dialog-provider,
   [  --enable-macosx-dialog-provider Mac OS X dialog module (default enabled on Mac OS X)])
 if test "x${enable_macosx_dialog_provider}" != "xno" &&
diff --git a/modules/video_output/Modules.am b/modules/video_output/Modules.am
index e2ccfdb..a19868f 100644
--- a/modules/video_output/Modules.am
+++ b/modules/video_output/Modules.am
@@ -15,6 +15,7 @@ SOURCES_hd1000v = hd1000v.cpp
 SOURCES_snapshot = snapshot.c
 SOURCES_opengl = opengl.c opengl.h
 SOURCES_directfb = directfb.c
+SOURCES_vout_calayer = calayer.m
 SOURCES_vmem = vmem.c
 SOURCES_yuv = yuv.c
 SOURCES_vout_wrapper = wrapper.c
diff --git a/modules/video_output/calayer.m b/modules/video_output/calayer.m
new file mode 100644
index 0000000..c70718e
--- /dev/null
+++ b/modules/video_output/calayer.m
@@ -0,0 +1,589 @@
+/*****************************************************************************
+ * calayer.m: MacOS X CALayer vout provider
+ *****************************************************************************
+ * Copyright (C) 2001-2009 the VideoLAN team
+ * $Id$
+ *
+ * Authors: David Menestrina <dmenest-vlc at ofb dot net>
+ *          Colin Delacroix <colin at zoy.org>
+ *          Florian G. Pflug <fgp at phlo.org>
+ *          Jon Lech Johansen <jon-vl at nanocrew.net>
+ *          Derk-Jan Hartman <hartman at videolan dot org>
+ *          Eric Petit <titer at m0k.org>
+ *          Benjamin Pracht <bigben at videolan dot org>
+ *          Damien Fouilleul <damienf at videolan dot org>
+ *          Pierre d'Herbemont <pdherbemont at videolan dot org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+
+#import <Cocoa/Cocoa.h>
+#import <QuartzCore/QuartzCore.h>
+#import <ApplicationServices/ApplicationServices.h>
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_vout_display.h>
+
+/**
+ *  This vout display module is written to support the Core Animation
+ *  drawing model for the mozilla plugin on 64-bit Mac.  Of course
+ *  it also can be used to embed VLC in any Mac application.
+ *
+ *  To use this vout, you must set the drawable-nsobject variable
+ *  to a CALayer * (this is done via the libvlc_media_player_set_nsobject()
+ *  function).  This module will create a CALayer containing the video
+ *  content and add it as a sublayer to the drawable-nsobject layer.
+ *  The sublayer will size itself to fill its superlayer (up to the current
+ *  aspect-ratio setting), and will automatically resize itself as the
+ *  superlayer's bounds change.  The sublayer also makes use of constraints
+ *  to keep its placement in the center of the superlayer.  Therefore,
+ *  the superlayer should use CAConstraintLayoutManager.
+ *
+ *  When fullscreen mode is invoked, this vout display will remove the
+ *  video layer from the superlayer, and add it to a fullscreen view.
+ *  On exit from fullscreen mode, the video layer will be again added as a
+ *  sublayer to the drawable-nsobject.
+ */
+
+/**
+ * Forward declarations
+ */
+static int Open(vlc_object_t *);
+static void Close(vlc_object_t *);
+static picture_pool_t *Pool(vout_display_t *vd, unsigned count);
+
+static void Display(vout_display_t *vd, picture_t *pic);
+static int Control(vout_display_t *vd, int query, va_list ap);
+
+/**
+ * Module declaration
+ */
+vlc_module_begin ()
+    /* Will be loaded even without interface module. see voutgl.m */
+    set_shortname("CALayer")
+    set_description( N_("Mac OS X CALayer video output (requires drawable-nsobject)"))
+    set_category(CAT_VIDEO)
+    set_subcategory(SUBCAT_VIDEO_VOUT )
+    set_capability("vout display", 0)
+    set_callbacks(Open, Close)
+
+    add_shortcut("calayer")
+    add_shortcut("vout_calayer")
+vlc_module_end ()
+
+ at interface VLCLayer : CALayer
+{
+    vout_display_t *vd;
+}
+- (id) initWithVd: (vout_display_t *) p_vd;
+- (void) resizeWithOldSuperlayerSize: (CGSize) size;
+ at end
+
+ at interface VLCFullscreenView : NSView
+{
+    vout_display_t *vd;
+}
+- (id) initWithVd: (vout_display_t *) p_vd;
+ at end
+
+struct vout_display_sys_t
+{
+    // The layer to put our pictures in.
+    CALayer *p_calayer;
+
+    // The picture pool.
+    picture_pool_t *p_pool;
+
+    // Because p_pool is an opaque type, we must keep copies of important internal
+    // pool data for access to the pictures' sys structures.
+    unsigned int picture_count;
+    picture_t **p_pictures;
+
+    // We create a layer and a view for fullscreen mode.
+    CALayer *p_fullscreenLayer;
+    VLCFullscreenView *p_fullscreenView;
+};
+
+typedef struct picture_sys_t
+{
+    // We store a data provider for each picture to avoid continually
+    // reallocating this structure.
+    CGDataProviderRef p_provider;
+} calayer_picture_sys_t;
+
+/**
+ *  This function sets the video layer's bounds to fit properly within
+ *  its superlayer for the current aspect ratio.  It is called whenever
+ *  the superlayer is resized, or when the aspect ratio changes.
+ */
+static void fixAspectRatio(vout_display_t *vd, const video_format_t *source)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    if( !sys->p_calayer )
+        return;
+
+    // Compute the layer's new bounds
+    vout_display_cfg_t place_cfg = *vd->cfg;
+    CGRect bounds = sys->p_calayer.superlayer.bounds;
+    place_cfg.display.width  = bounds.size.width;
+    place_cfg.display.height = bounds.size.height;
+    vout_display_place_t place;
+    vout_display_PlacePicture(&place, source, &place_cfg, FALSE);
+
+    // Set the layer's bounds
+    [CATransaction begin];
+    sys->p_calayer.bounds = CGRectMake(place.x, place.y, place.width, place.height);
+    [CATransaction commit];
+}
+
+/**
+ *  VLCLayer is a simple subclass of CALayer.  It stores a backpointer to
+ *  the vout_display_t, and handles the resizing of the superlayer.
+ */
+ at implementation VLCLayer
+- (id) initWithVd: (vout_display_t *) p_vd;
+{
+    if( self = [super init] )
+        vd = p_vd;
+
+    return self;
+}
+- (void) dealloc
+{
+    vd = NULL;
+    [super dealloc];
+}
+- (void) resizeWithOldSuperlayerSize: (CGSize) size
+{
+    fixAspectRatio(vd, &vd->source);
+}
+ at end
+
+/**
+ *  VLCFullscreenView is a simple subclass of NSView.  Its main purpose is
+ *  to handle events in fullscreen mode.
+ */
+ at implementation VLCFullscreenView
+- (id) initWithVd: (vout_display_t *) p_vd
+{
+    if( self = [super initWithFrame: [[NSScreen mainScreen] frame]] )
+        vd = p_vd;
+
+    return self;
+}
+- (void) dealloc
+{
+    vd = NULL;
+    [super dealloc];
+}
+- (BOOL) acceptsFirstResponder
+{
+    // We must return YES here if we wish to receive keyboard events.
+    return YES;
+}
+- (void) cancelOperation: (id) sender
+{
+    // Command-period is handled here.
+    // Also, the Escape key is handled here as long as the
+    // Escape keyDown event is passed up to the superclass.
+    vout_display_SendEventFullscreen (vd, false);
+}
+- (void) mouseDown: (NSEvent *) event
+{
+    // Exit fullscreen on double click.
+    if( event.clickCount > 1 )
+        vout_display_SendEventFullscreen (vd, false);
+    else
+        [super mouseDown: event];
+}
+ at end
+
+/**
+ *  This function changes the video layer's superlayer.  The layer
+ *  parameter passed in may be NULL, in which case the layer is simply
+ *  removed from its current superlayer.
+ */
+static void setLayer(vout_display_t *vd, CALayer *layer)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    [CATransaction begin];
+
+    // When switching superlayers, we want to disable animations.
+    [CATransaction setValue:(id)kCFBooleanTrue
+                   forKey:kCATransactionDisableActions];
+
+    [sys->p_calayer removeFromSuperlayer];
+    if( layer )
+    {
+        [layer addSublayer:sys->p_calayer];
+
+        // Move our layer to the center of the superlayer.
+        // This is something that will happen due to our
+        // constraints anyways, but if we do it here manually
+        // then we avoid it animating into position.
+        CGRect bounds = sys->p_calayer.superlayer.bounds;
+        sys->p_calayer.position = CGPointMake(bounds.size.width/2, bounds.size.height/2);
+
+        fixAspectRatio(vd, &vd->source);
+    }
+
+    [CATransaction commit];
+}
+
+static CALayer *getDrawable(vout_display_t *vd)
+{
+    CALayer *layer = var_CreateGetAddress(vd, "drawable-nsobject");
+    var_Destroy(vd, "drawable-nsobject");
+    return layer;
+}
+
+/**
+ *  Create and store the fullscreen layer and view for fullscreen mode,
+ *  storing them in the sys structure.
+ */
+static void createFullscreenView(vout_display_t *vd)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    sys->p_fullscreenLayer = [[CALayer layer] retain];
+    sys->p_fullscreenLayer.layoutManager=[CAConstraintLayoutManager layoutManager];
+    sys->p_fullscreenLayer.backgroundColor = CGColorGetConstantColor(kCGColorBlack);
+
+    sys->p_fullscreenView = [[VLCFullscreenView alloc] initWithVd: vd];
+    [sys->p_fullscreenView setLayer:sys->p_fullscreenLayer];
+    [sys->p_fullscreenView setWantsLayer:YES];
+}
+
+/**
+ *  Destroy the fullscreen layer and view.
+ */
+static void destroyFullscreenView(vout_display_t *vd)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    [sys->p_fullscreenLayer release];
+    sys->p_fullscreenLayer = Nil;
+    [sys->p_fullscreenView release];
+    sys->p_fullscreenView = Nil;
+}
+
+/**
+ *  Enter or exit fullscreen mode, based on the boolean fs parameter,
+ *  TRUE to enter fullscreen, FALSE to exit fullscreen.
+ */
+static void changeFullscreen(vout_display_t *vd, int fs)
+{
+    vout_display_sys_t *sys = vd->sys;
+    CALayer *newLayer;
+
+    [CATransaction begin];
+    [CATransaction setValue:(id)kCFBooleanTrue
+                   forKey:kCATransactionDisableActions];
+
+    // Have the view enter or leave fullscreen mode
+    if( fs )
+    {
+        [sys->p_fullscreenView enterFullScreenMode:[NSScreen mainScreen]
+                               withOptions:Nil];
+
+        // in fullscreen mode, we add ourselves to the fullscreen layer
+        newLayer = sys->p_fullscreenLayer;
+    }
+    else
+    {
+        [sys->p_fullscreenView exitFullScreenModeWithOptions:Nil];
+
+        // when not in fullscreen mode, we add ourselves to the layer
+        // in the drawable-nsobject variable.
+        newLayer = getDrawable(vd);
+    }
+
+    // add ourselves to the proper layer
+    setLayer(vd, newLayer);
+
+    [CATransaction commit];
+}
+
+static int Open(vlc_object_t *this)
+{
+    vout_display_t *vd = (vout_display_t *)this;
+    vout_display_sys_t *sys = calloc(1, sizeof(*sys));
+    vd->sys = sys;
+
+    if( !sys )
+        return VLC_ENOMEM;
+
+    [CATransaction begin];
+    // Create our layer
+    sys->p_calayer = [[VLCLayer alloc] initWithVd:vd];
+
+    // Add constraints to keep our layer in the center of the superlayer
+    [sys->p_calayer addConstraint:[CAConstraint
+                                   constraintWithAttribute:kCAConstraintMidY
+                                   relativeTo:@"superlayer"
+                                   attribute:kCAConstraintMidY]];
+    [sys->p_calayer addConstraint:[CAConstraint
+                                   constraintWithAttribute:kCAConstraintMidX
+                                   relativeTo:@"superlayer"
+                                   attribute:kCAConstraintMidX]];
+
+    // Create the components for fullscreen mode
+    createFullscreenView(vd);
+    [CATransaction commit];
+
+
+    // Get and add our layer to the superlayer
+    CALayer *layer = getDrawable(vd);
+    if( !layer )
+    {
+        msg_Dbg(vd, "No drawable-nsobject, passing over.");
+        goto error;
+    }
+    setLayer(vd, layer);
+
+    // Apple docs mention RGBA-32, so we'll do that.
+    video_format_t fmt = vd->fmt;
+    fmt.i_chroma = VLC_CODEC_RGBA;
+
+    vout_display_info_t info = vd->info;
+    info.has_pictures_invalid = false;
+
+    // Setup fields in vd
+    vd->pool = Pool;
+    vd->prepare = NULL;
+    vd->display = Display;
+    vd->control = Control;
+    vd->manage = NULL;
+    vd->info = info;
+    vd->fmt = fmt;
+
+    vout_display_SendEventFullscreen (vd, false);
+    vout_display_SendEventDisplaySize (vd, vd->source.i_visible_width, vd->source.i_visible_height, false);
+
+    return VLC_SUCCESS;
+
+error:
+    Close(this);
+    return VLC_EGENERIC;
+}
+
+/*****************************************************************************
+ * vout display callbacks
+ *****************************************************************************/
+
+/**
+ *  This function adds module specific "sys" data to the given picture_t.
+ *  We cache a CGDataProvider for each picture_t.
+ */
+static void fillPictureSysData(picture_t *pic)
+{
+    calayer_picture_sys_t *picsys = pic->p_sys = calloc(1, sizeof(calayer_picture_sys_t));
+
+    // Create the data provider/CGImage
+    CGDataProviderRef data = CGDataProviderCreateWithData(NULL,
+      pic->p[0].p_pixels, pic->p[0].i_pitch * pic->p[0].i_lines, NULL);
+
+    picsys->p_provider = data;
+}
+
+/**
+ *  This funtion frees the module specific data (created by fillPictureSysData)
+ *  in the given picture_t.
+ */
+static void freePictureSysData(picture_t *pic)
+{
+    if( pic->p_sys )
+    {
+        CGDataProviderRelease(pic->p_sys->p_provider);
+        free(pic->p_sys);
+        pic->p_sys = NULL;
+    }
+}
+
+/**
+ *  This function creates our picture pool and stores it in our sys structure.
+ */
+static void fillPool(vout_display_t *vd, unsigned count)
+{
+    picture_t **picture = calloc(count, sizeof *picture);
+    if( !picture )
+        return;
+
+    // Create count pictures, and add sys data to each one.
+    for( int i = 0; i < count; i++ ) {
+        picture[i] = picture_NewFromFormat(&vd->fmt);
+        if( !picture[i] )
+            goto error;
+        fillPictureSysData(picture[i]);
+    }
+
+    // Create the pool from the picture array
+    picture_pool_t *pool = picture_pool_New(count, picture);
+    if( !pool )
+        goto error;
+
+    // Store the relevant information in our sys structure.
+    vd->sys->p_pool = pool;
+    vd->sys->p_pictures = picture;
+    vd->sys->picture_count = count;
+    return;
+
+error:
+    for( int i = 0; i < count; i++ ) {
+        if( !picture[i] )
+            break;
+        picture_Release(picture[i]);
+    }
+    free(picture);
+    return;
+}
+
+/**
+ *  This function frees the picture pool created by fillPool().
+ */
+static void freePool(vout_display_sys_t *sys)
+{
+    picture_pool_t *pool = sys->p_pool;
+    if( !pool )
+        return;
+
+    // free the sys data from each picture
+    for( int i = 0; i < sys->picture_count; i++ )
+       freePictureSysData(sys->p_pictures[i]);
+
+    // free the pool itself
+    picture_pool_Delete(pool);
+
+    sys->p_pool = NULL;
+
+    if( sys->p_pictures )
+        free(sys->p_pictures);
+    sys->p_pictures = NULL;
+}
+
+static picture_pool_t *Pool(vout_display_t *vd, unsigned count)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    if( !sys->p_pool )
+    {
+        fillPool(vd, count);
+    }
+    assert(sys->p_pool);
+    return sys->p_pool;
+}
+
+static void Display(vout_display_t *vd, picture_t *pic)
+{
+    vout_display_sys_t *sys = vd->sys;
+    assert(pic->i_planes == 1);
+    assert(pic->format.i_bits_per_pixel == 32);
+
+    // Create a CGImage to put in the layer
+    CGImageRef image = CGImageCreate(pic->format.i_width, pic->format.i_height,
+      8, 32, pic->p[0].i_pitch, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaNoneSkipLast |
+      kCGBitmapByteOrder32Big, pic->p_sys->p_provider, NULL, TRUE,
+      kCGRenderingIntentDefault);
+
+    // Because this thread doesn't have a runloop, we must use explicit transactions
+    // when updating the layer's contents.
+    [CATransaction begin];
+    sys->p_calayer.contents = (id) image;
+    [CATransaction commit];
+
+    CGImageRelease(image);
+
+    picture_Release (pic);
+}
+
+static int Control (vout_display_t *vd, int query, va_list ap)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    switch (query)
+    {
+        case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
+        case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
+        case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
+        case VOUT_DISPLAY_CHANGE_ZOOM:
+        case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
+        {
+            /* todo */
+            return VLC_EGENERIC;
+        }
+        case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
+        {
+            const vout_display_cfg_t *cfg = vd->cfg;
+            const video_format_t *source = va_arg(ap, const video_format_t *);
+
+            fixAspectRatio(vd, source);
+
+            return VLC_SUCCESS;
+        }
+        case VOUT_DISPLAY_CHANGE_FULLSCREEN:
+        {
+            vout_display_cfg_t cfg = *va_arg(ap, const vout_display_cfg_t *);
+
+            changeFullscreen(vd, cfg.is_fullscreen);
+
+            return VLC_SUCCESS;
+        }
+
+        case VOUT_DISPLAY_HIDE_MOUSE:
+            return VLC_SUCCESS;
+
+        case VOUT_DISPLAY_GET_OPENGL:
+        {
+            return VLC_EGENERIC;
+        }
+        case VOUT_DISPLAY_RESET_PICTURES:
+            assert (0);
+        default:
+            msg_Err (vd, "Unknown request in Mac OS X CALayer vout display");
+            return VLC_EGENERIC;
+    }
+}
+
+void Close(vlc_object_t *this)
+{
+    vout_display_t *vd = (vout_display_t *)this;
+
+    if( vd->sys )
+    {
+        vout_display_sys_t *sys = vd->sys;
+
+        destroyFullscreenView(vd);
+
+        // Release the CALayer
+        setLayer(vd, NULL);
+        [sys->p_calayer release];
+        sys->p_calayer = NULL;
+
+        freePool(sys);
+        free(sys);
+        vd->sys = NULL;
+    }
+}
-- 
1.7.0.3




More information about the vlc-devel mailing list