From 70bc0a33a6a67599fa85042f483d3e5aa3bcb45e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Felix=20Paul=20K=C3=BChne?= Date: Sun, 21 Apr 2013 17:12:13 +0200 Subject: [PATCH] New CoreGraphics based video output drawing on CoreAnimation layers --- configure.ac | 9 + modules/video_output/Modules.am | 1 + modules/video_output/coregraphicslayer.m | 387 +++++++++++++++++++++++++++++++ 3 files changed, 397 insertions(+) create mode 100644 modules/video_output/coregraphicslayer.m diff --git a/configure.ac b/configure.ac index 7c9362031b..382f982247 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/modules/video_output/Modules.am b/modules/video_output/Modules.am index c52ab3bb02..b1199aa0e1 100644 --- a/modules/video_output/Modules.am +++ b/modules/video_output/Modules.am @@ -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 diff --git a/modules/video_output/coregraphicslayer.m b/modules/video_output/coregraphicslayer.m new file mode 100644 index 0000000000..824e806f61 --- /dev/null +++ b/modules/video_output/coregraphicslayer.m @@ -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 + * + * 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 +#import +#import +#import + +#import +#import + +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 +- (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 *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 -- 2.11.4.GIT