New CoreGraphics based video output drawing on CoreAnimation layers

This commit is contained in:
Felix Paul Kühne 2013-04-21 17:12:13 +02:00
parent a01f401773
commit 70bc0a33a6
3 changed files with 397 additions and 0 deletions

View File

@ -3119,6 +3119,15 @@ then
VLC_ADD_PLUGIN([vout_macosx])
fi
dnl
dnl Mac CoreGraphics Layer Vout
AC_ARG_ENABLE(coregraphicslayer-vout,
[ --enable-coregraphicslayer-vout CoreGraphics layered video output module (default disabled)])
if test "${enable_coregraphicslayer_vout}" = "yes"; then
VLC_ADD_LIBS([vout_coregraphicslayer],[-Wl,-framework,Cocoa])
VLC_ADD_LIBS([vout_coregraphicslayer],[-Wl,-framework,QuartzCore])
VLC_ADD_PLUGIN([vout_coregraphicslayer])
fi
dnl
dnl freetype module

View File

@ -7,6 +7,7 @@ SOURCES_directfb = directfb.c
SOURCES_vmem = vmem.c
SOURCES_yuv = yuv.c
SOURCES_vout_macosx = macosx.m opengl.h opengl.c
SOURCES_vout_coregraphicslayer = coregraphicslayer.m
SOURCES_vout_ios2 = ios2.m opengl.h opengl.c
SOURCES_android_surface = androidsurface.c

View File

@ -0,0 +1,387 @@
/*****************************************************************************
* coregraphicslayer.m: CoreGraphics video output for NPAPI plugins
*****************************************************************************
* Copyright (C) 2013 VLC authors and VideoLAN
* $Id$
*
* Authors: Felix Paul Kühne <fkuehne # videolan.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# import "config.h"
#endif
#import <vlc_common.h>
#import <vlc_plugin.h>
#import <vlc_vout_display.h>
#import <vlc_picture_pool.h>
#import <QuartzCore/QuartzCore.h>
#import <AppKit/AppKit.h>
static int Open(vlc_object_t *);
static void Close(vlc_object_t *);
vlc_module_begin ()
set_description(N_("CoreGraphics video output"))
set_shortname("CoreGraphics")
set_category(CAT_VIDEO)
set_subcategory(SUBCAT_VIDEO_VOUT)
set_capability("vout display", 0)
set_callbacks(Open, Close)
vlc_module_end ()
@protocol VLCCoreGraphicsVideoLayerEmbedding <NSObject>
- (void)addVoutLayer:(CALayer *)aLayer;
- (void)removeVoutLayer:(CALayer *)aLayer;
- (CGSize)currentOutputSize;
@end
@interface VLCCoreGraphicsLayer : CALayer {
CGImageRef _lastFrame;
bool lock;
vout_display_t *_vd;
}
@property (nonatomic) vout_display_t *vd;
- (void)setLastFrame:(CGImageRef)lastFrame;
@end
/*****************************************************************************
* Local prototypes
*****************************************************************************/
struct vout_display_sys_t {
picture_pool_t *pool;
picture_resource_t resource;
VLCCoreGraphicsLayer *cgLayer;
vout_window_t *embed;
bool has_first_frame;
size_t componentsPerPixel;
size_t bitsPerComponent;
size_t bitsPerPixel;
CALayer <VLCCoreGraphicsVideoLayerEmbedding> *container;
CGColorSpaceRef colorspace;
};
static picture_pool_t *Pool(vout_display_t *, unsigned);
static void Display(vout_display_t *, picture_t *, subpicture_t *);
static int Control(vout_display_t *, int, va_list);
/*****************************************************************************
* OpenVideo: activates dummy vout display method
*****************************************************************************/
static int Open(vlc_object_t *object)
{
vout_display_t *vd = (vout_display_t *)object;
vout_display_sys_t *sys = calloc(1, sizeof(*sys));
NSAutoreleasePool *nsPool = nil;
if (!sys)
return VLC_ENOMEM;
sys->pool = NULL;
id container = var_CreateGetAddress(vd, "drawable-nsobject");
if (container)
vout_display_DeleteWindow(vd, NULL);
else {
vout_window_cfg_t wnd_cfg;
memset(&wnd_cfg, 0, sizeof(wnd_cfg));
wnd_cfg.type = VOUT_WINDOW_TYPE_NSOBJECT;
wnd_cfg.x = var_InheritInteger(vd, "video-x");
wnd_cfg.y = var_InheritInteger(vd, "video-y");
wnd_cfg.height = vd->cfg->display.height;
wnd_cfg.width = vd->cfg->display.width;
sys->embed = vout_display_NewWindow(vd, &wnd_cfg);
if (sys->embed)
container = sys->embed->handle.nsobject;
if (!container) {
msg_Err(vd, "No drawable-nsobject found!");
goto bailout;
}
}
/* store for later, released in Close() */
sys->container = [container retain];
/* allocate pool */
nsPool = [[NSAutoreleasePool alloc] init];
if ([container respondsToSelector:@selector(addVoutLayer:)]) {
msg_Dbg(vd, "container implements implicit protocol");
sys->cgLayer = [[VLCCoreGraphicsLayer alloc] init];
[container addVoutLayer:sys->cgLayer];
} else if ([container respondsToSelector:@selector(addSublayer:)] || [container isKindOfClass:[CALayer class]]) {
msg_Dbg(vd, "container doesn't implement implicit protocol, fallback mode used");
sys->cgLayer = [[VLCCoreGraphicsLayer alloc] init];
[container addSublayer:sys->cgLayer];
} else {
msg_Err(vd, "Provided NSObject container isn't compatible");
goto bailout;
}
[sys->cgLayer setVd:vd];
/* setup output format */
video_format_t fmt = vd->fmt;
char *chroma = "RGBA";
fmt.i_chroma = vlc_fourcc_GetCodecFromString(VIDEO_ES, chroma);
msg_Dbg(vd, "forcing chroma 0x%.8x (%4.4s)", fmt.i_chroma, (char*)&fmt.i_chroma);
video_format_FixRgb(&fmt);
msg_Dbg(vd, "will use pixel format %4.4s", (char*)&fmt.i_chroma);
/* setup vout display */
vout_display_info_t info = vd->info;
info.has_hide_mouse = false;
vd->sys = sys;
vd->fmt = fmt;
vd->info = info;
vd->pool = Pool;
vd->display = Display;
vd->control = Control;
vd->prepare = NULL;
vd->manage = NULL;
/* setup initial state */
CGSize outputSize;
if ([container respondsToSelector:@selector(currentOutputSize)])
outputSize = [container currentOutputSize];
else
outputSize = [sys->container visibleRect].size;
vout_display_SendEventFullscreen(vd, false);
vout_display_SendEventDisplaySize(vd, (int)outputSize.width, (int)outputSize.height, false);
sys->colorspace = CGColorSpaceCreateDeviceRGB();
sys->componentsPerPixel = 4;
sys->bitsPerComponent = sizeof(unsigned char) * 8;
sys->bitsPerPixel = sys->bitsPerComponent * sys->componentsPerPixel;
return VLC_SUCCESS;
bailout:
if (nsPool)
[nsPool release];
return VLC_EGENERIC;
}
static void Close(vlc_object_t *object)
{
vout_display_t *vd = (vout_display_t *)object;
vout_display_sys_t *sys = vd->sys;
if (sys->pool)
picture_pool_Delete(sys->pool);
if (sys->cgLayer) {
if ([sys->container respondsToSelector:@selector(removeVoutLayer:)])
[sys->container removeVoutLayer:sys->cgLayer];
else
[sys->cgLayer removeFromSuperlayer];
[sys->cgLayer release];
}
if (sys->container)
[sys->container release];
free(sys);
}
static picture_pool_t *Pool(vout_display_t *vd, unsigned count)
{
vout_display_sys_t *sys = vd->sys;
if (!sys->pool)
sys->pool = picture_pool_NewFromFormat(&vd->fmt, count);
return sys->pool;
}
static void Display(vout_display_t *vd, picture_t *picture, subpicture_t *subpicture)
{
vout_display_sys_t *sys = vd->sys;
if (!sys->cgLayer) {
msg_Warn(vd, "no cglayer to Display in");
return;
}
uint32_t sourceWidth, sourceHeight;
sourceWidth = picture->p[0].i_visible_pitch / picture->p[0].i_pixel_pitch;
sourceHeight = picture->p[0].i_visible_lines;
const int crop_offset = vd->source.i_y_offset * picture->p->i_pitch +
vd->source.i_x_offset * picture->p->i_pixel_pitch;
CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
&picture->p->p_pixels[crop_offset],
sizeof(picture->p->p_pixels[crop_offset]),
kCFAllocatorNull);
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData(dataRef);
CGImageRef newFrame = CGImageCreate(sourceWidth,
sourceHeight,
sys->bitsPerComponent,
sys->bitsPerPixel,
sys->componentsPerPixel * sourceWidth,
sys->colorspace,
kCGBitmapByteOrder16Big,
dataProvider,
NULL,
true,
kCGRenderingIntentPerceptual);
CGDataProviderRelease(dataProvider);
if (!newFrame)
goto end;
[sys->cgLayer setLastFrame:newFrame];
end:
VLC_UNUSED(subpicture);
picture_Release(picture);
}
static int Control(vout_display_t *vd, int query, va_list args)
{
VLC_UNUSED(vd);
VLC_UNUSED(query);
VLC_UNUSED(args);
return VLC_SUCCESS;
}
@implementation VLCCoreGraphicsLayer
@synthesize vd=_vd;
- (id)init
{
if (self = [super init]) {
[CATransaction begin];
self.needsDisplayOnBoundsChange = YES;
self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
[CATransaction commit];
}
return self;
}
- (bool)locked
{
return lock;
}
- (void)setLastFrame:(CGImageRef)lastFrame
{
if (lock) {
/* drop frame since we currently drawing */
CGImageRelease(lastFrame);
return;
}
_lastFrame = CGImageCreateCopy(lastFrame);
CGImageRelease(lastFrame);
CGRect invalidRect = CGRectMake(0, 0, CGImageGetWidth(_lastFrame), CGImageGetHeight(_lastFrame));
[CATransaction begin];
[self setNeedsDisplayInRect:invalidRect];
[CATransaction commit];
}
- (void)drawInContext:(CGContextRef)cgContext
{
if (!cgContext)
return;
if (!_lastFrame)
return;
if (lock)
return;
lock = YES;
CGRect layerRect = self.bounds;
float display_width = 0.;
float display_height = 0.;
float media_width = CGImageGetWidth(_lastFrame);
float media_height = CGImageGetHeight(_lastFrame);
float src_aspect = (float)media_width / media_height;
float dst_aspect = (float)layerRect.size.width/layerRect.size.height;
if (src_aspect > dst_aspect) {
if (layerRect.size.width != media_width) { //don't scale if size equal
display_width = layerRect.size.width;
display_height = display_width / src_aspect; // + 0.5);
} else {
display_width = media_width;
display_height = media_height;
}
} else {
if (layerRect.size.height != media_height) { //don't scale if size equal
display_height = layerRect.size.height;
display_width = display_height * src_aspect; // + 0.5);
} else {
display_width = media_width;
display_height = media_height;
}
}
/* Compute the position of the video */
float left = (layerRect.size.width - display_width) / 2.;
float top = (layerRect.size.height - display_height) / 2.;
CGContextSaveGState(cgContext);
// draw a clear background
CGContextClearRect(cgContext, CGRectMake(0., 0., layerRect.size.width, layerRect.size.height));
// draw the image
CGRect rect = CGRectMake(left, top, display_width, display_height);
CGContextDrawImage(cgContext, rect, _lastFrame);
lock = NO;
CGContextRestoreGState(cgContext);
}
- (void)resizeWithOldSuperlayerSize:(CGSize)size
{
[super resizeWithOldSuperlayerSize:size];
if (!_vd)
return;
CGSize outputSize = [_vd->sys->container currentOutputSize];
vout_display_place_t place;
vout_display_cfg_t cfg_tmp = *(_vd->cfg);
cfg_tmp.display.width = outputSize.width;
cfg_tmp.display.height = outputSize.height;
vout_display_PlacePicture (&place, &_vd->source, &cfg_tmp, false);
vout_display_SendEventDisplaySize (_vd, outputSize.width, outputSize.height, _vd->cfg->is_fullscreen);
}
@end