mirror of https://code.videolan.org/videolan/vlc
685 lines
19 KiB
Objective-C
685 lines
19 KiB
Objective-C
/*****************************************************************************
|
|
* VLCSampleBufferDisplay.m: video output display using
|
|
* AVSampleBufferDisplayLayer on macOS
|
|
*****************************************************************************
|
|
* Copyright (C) 2023 VLC authors and VideoLAN
|
|
*
|
|
* Authors: Maxime Chapelet <umxprime at videolabs dot io>
|
|
*
|
|
*
|
|
* 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.
|
|
*****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_filter.h>
|
|
#include <vlc_plugin.h>
|
|
#include <vlc_modules.h>
|
|
#include <vlc_vout_display.h>
|
|
#include <vlc_atomic.h>
|
|
|
|
# import <TargetConditionals.h>
|
|
# if TARGET_OS_OSX
|
|
# import <Cocoa/Cocoa.h>
|
|
# define VLCView NSView
|
|
# else
|
|
# import <Foundation/Foundation.h>
|
|
# import <UIKit/UIKit.h>
|
|
# define VLCView UIView
|
|
# endif
|
|
|
|
#import <AVFoundation/AVFoundation.h>
|
|
#import <AVKit/AVKit.h>
|
|
|
|
#include "../../codec/vt_utils.h"
|
|
|
|
static vlc_decoder_device * CVPXHoldDecoderDevice(vlc_object_t *o, void *sys)
|
|
{
|
|
VLC_UNUSED(o);
|
|
vout_display_t *vd = sys;
|
|
vlc_decoder_device *device =
|
|
vlc_decoder_device_Create(VLC_OBJECT(vd), vd->cfg->window);
|
|
static const struct vlc_decoder_device_operations ops =
|
|
{
|
|
NULL,
|
|
};
|
|
device->ops = &ops;
|
|
device->type = VLC_DECODER_DEVICE_VIDEOTOOLBOX;
|
|
return device;
|
|
}
|
|
|
|
static filter_t *
|
|
CreateCVPXConverter(vout_display_t *vd)
|
|
{
|
|
filter_t *converter = vlc_object_create(vd, sizeof(filter_t));
|
|
if (!converter)
|
|
return NULL;
|
|
|
|
static const struct filter_video_callbacks cbs =
|
|
{
|
|
.buffer_new = NULL,
|
|
.hold_device = CVPXHoldDecoderDevice,
|
|
};
|
|
converter->owner.video = &cbs;
|
|
converter->owner.sys = vd;
|
|
|
|
es_format_InitFromVideo( &converter->fmt_in, vd->fmt );
|
|
es_format_InitFromVideo( &converter->fmt_out, vd->fmt );
|
|
|
|
converter->fmt_out.video.i_chroma =
|
|
converter->fmt_out.i_codec = VLC_CODEC_CVPX_BGRA;
|
|
|
|
converter->p_module = module_need(converter, "video converter", NULL, false);
|
|
if (!converter->p_module)
|
|
{
|
|
vlc_object_delete(converter);
|
|
return NULL;
|
|
}
|
|
assert( converter->ops != NULL );
|
|
|
|
return converter;
|
|
}
|
|
|
|
|
|
static void DeleteCVPXConverter( filter_t * p_converter )
|
|
{
|
|
if (!p_converter)
|
|
return;
|
|
|
|
if( p_converter->p_module )
|
|
{
|
|
filter_Close( p_converter );
|
|
module_unneed( p_converter, p_converter->p_module );
|
|
}
|
|
|
|
es_format_Clean( &p_converter->fmt_in );
|
|
es_format_Clean( &p_converter->fmt_out );
|
|
|
|
vlc_object_delete(p_converter);
|
|
}
|
|
|
|
/**
|
|
* Protocol declaration that drawable-nsobject should follow
|
|
*/
|
|
@protocol VLCOpenGLVideoViewEmbedding <NSObject>
|
|
- (void)addVoutSubview:(VLCView *)view;
|
|
- (void)removeVoutSubview:(VLCView *)view;
|
|
@end
|
|
|
|
#pragma mark -
|
|
@class VLCSampleBufferSubpicture, VLCSampleBufferDisplay;
|
|
|
|
@interface VLCSampleBufferSubpictureRegion: NSObject
|
|
@property (nonatomic, weak) VLCSampleBufferSubpicture *subpicture;
|
|
@property (nonatomic) CGRect backingFrame;
|
|
@property (nonatomic) CGImageRef image;
|
|
@end
|
|
|
|
@implementation VLCSampleBufferSubpictureRegion
|
|
- (void)dealloc {
|
|
CGImageRelease(_image);
|
|
}
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
@interface VLCSampleBufferSubpicture: NSObject
|
|
@property (nonatomic, weak) VLCSampleBufferDisplay *sys;
|
|
@property (nonatomic) NSArray<VLCSampleBufferSubpictureRegion *> *regions;
|
|
@property (nonatomic) int64_t order;
|
|
@end
|
|
|
|
@implementation VLCSampleBufferSubpicture
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
@interface VLCSampleBufferSubpictureView: VLCView
|
|
- (void)drawSubpicture:(VLCSampleBufferSubpicture *)subpicture;
|
|
@end
|
|
|
|
@implementation VLCSampleBufferSubpictureView
|
|
{
|
|
VLCSampleBufferSubpicture *_pendingSubpicture;
|
|
}
|
|
|
|
- (instancetype)init {
|
|
self = [super init];
|
|
if (!self)
|
|
return nil;
|
|
#if TARGET_OS_OSX
|
|
self.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
self.wantsLayer = YES;
|
|
#else
|
|
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
self.backgroundColor = [UIColor clearColor];
|
|
#endif
|
|
return self;
|
|
}
|
|
|
|
- (void)drawSubpicture:(VLCSampleBufferSubpicture *)subpicture {
|
|
_pendingSubpicture = subpicture;
|
|
#if TARGET_OS_OSX
|
|
[self setNeedsDisplay:YES];
|
|
#else
|
|
[self setNeedsDisplay];
|
|
#endif
|
|
}
|
|
|
|
- (void)drawRect:(CGRect)dirtyRect {
|
|
#if TARGET_OS_OSX
|
|
NSGraphicsContext *graphicsCtx = [NSGraphicsContext currentContext];
|
|
CGContextRef cgCtx = [graphicsCtx CGContext];
|
|
#else
|
|
CGContextRef cgCtx = UIGraphicsGetCurrentContext();
|
|
#endif
|
|
|
|
CGContextClearRect(cgCtx, self.bounds);
|
|
|
|
#if TARGET_OS_IPHONE
|
|
CGContextSaveGState(cgCtx);
|
|
CGAffineTransform translate = CGAffineTransformTranslate(CGAffineTransformIdentity, 0.0, self.frame.size.height);
|
|
CGFloat scale = 1.0f / self.contentScaleFactor;
|
|
CGAffineTransform transform = CGAffineTransformScale(translate, scale, -scale);
|
|
CGContextConcatCTM(cgCtx, transform);
|
|
#endif
|
|
VLCSampleBufferSubpictureRegion *region;
|
|
for (region in _pendingSubpicture.regions) {
|
|
#if TARGET_OS_OSX
|
|
CGRect regionFrame = [self convertRectFromBacking:region.backingFrame];
|
|
#else
|
|
CGRect regionFrame = region.backingFrame;
|
|
#endif
|
|
CGContextDrawImage(cgCtx, regionFrame, region.image);
|
|
}
|
|
#if TARGET_OS_IPHONE
|
|
CGContextRestoreGState(cgCtx);
|
|
#endif
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
@interface VLCSampleBufferDisplayView: VLCView <CALayerDelegate>
|
|
@property (nonatomic, readonly) vout_display_t *vd;
|
|
- (instancetype)initWithVoutDisplay:(vout_display_t *)vd;
|
|
- (AVSampleBufferDisplayLayer *)displayLayer;
|
|
@end
|
|
|
|
@implementation VLCSampleBufferDisplayView
|
|
|
|
- (instancetype)initWithVoutDisplay:(vout_display_t *)vd {
|
|
self = [super init];
|
|
if (!self)
|
|
return nil;
|
|
_vd = vd;
|
|
#if TARGET_OS_OSX
|
|
self.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
self.wantsLayer = YES;
|
|
#else
|
|
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
#endif
|
|
return self;
|
|
}
|
|
|
|
#if TARGET_OS_OSX
|
|
- (CALayer *)makeBackingLayer {
|
|
AVSampleBufferDisplayLayer *layer;
|
|
layer = [AVSampleBufferDisplayLayer new];
|
|
layer.delegate = self;
|
|
layer.videoGravity = AVLayerVideoGravityResizeAspect;
|
|
[CATransaction lock];
|
|
layer.needsDisplayOnBoundsChange = YES;
|
|
layer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
|
|
layer.opaque = 1.0;
|
|
layer.hidden = NO;
|
|
[CATransaction unlock];
|
|
return layer;
|
|
}
|
|
#else
|
|
+ (Class)layerClass {
|
|
return [AVSampleBufferDisplayLayer class];
|
|
}
|
|
#endif
|
|
|
|
- (AVSampleBufferDisplayLayer *)displayLayer {
|
|
return (AVSampleBufferDisplayLayer *)self.layer;
|
|
}
|
|
|
|
#if TARGET_OS_OSX
|
|
/* Layer delegate method that ensures the layer always get the
|
|
* correct contentScale based on whether the view is on a HiDPI
|
|
* display or not, and when it is moved between displays.
|
|
*/
|
|
- (BOOL)layer:(CALayer *)layer
|
|
shouldInheritContentsScale:(CGFloat)newScale
|
|
fromWindow:(NSWindow *)window
|
|
{
|
|
return YES;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* General properties
|
|
*/
|
|
|
|
- (BOOL)isOpaque
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)acceptsFirstResponder
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
@interface VLCSampleBufferDisplay: NSObject {
|
|
@public
|
|
vout_display_place_t place;
|
|
filter_t *converter;
|
|
}
|
|
@property (nonatomic) id<VLCOpenGLVideoViewEmbedding> container;
|
|
@property (nonatomic) VLCSampleBufferDisplayView *displayView;
|
|
@property (nonatomic) AVSampleBufferDisplayLayer *displayLayer;
|
|
@property (nonatomic) VLCSampleBufferSubpictureView *spuView;
|
|
@property (nonatomic) VLCSampleBufferSubpicture *subpicture;
|
|
@end
|
|
|
|
@implementation VLCSampleBufferDisplay
|
|
@end
|
|
|
|
#pragma mark -
|
|
#pragma mark Module functions
|
|
|
|
static void Close(vout_display_t *vd)
|
|
{
|
|
VLCSampleBufferDisplay *sys;
|
|
sys = (__bridge_transfer VLCSampleBufferDisplay*)vd->sys;
|
|
|
|
DeleteCVPXConverter(sys->converter);
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if ([sys.container respondsToSelector:@selector(removeVoutSubview:)]) {
|
|
[sys.container removeVoutSubview:sys.displayView];
|
|
}
|
|
[sys.displayView removeFromSuperview];
|
|
[sys.spuView removeFromSuperview];
|
|
});
|
|
}
|
|
|
|
static void RenderPicture(vout_display_t *vd, picture_t *pic, vlc_tick_t date) {
|
|
VLCSampleBufferDisplay *sys;
|
|
sys = (__bridge VLCSampleBufferDisplay*)vd->sys;
|
|
|
|
@synchronized(sys.displayLayer) {
|
|
if (sys.displayLayer == nil)
|
|
return;
|
|
}
|
|
|
|
picture_Hold(pic);
|
|
|
|
picture_t *dst = pic;
|
|
if (sys->converter) {
|
|
dst = sys->converter->ops->filter_video(sys->converter, pic);
|
|
}
|
|
|
|
CVPixelBufferRef pixelBuffer = cvpxpic_get_ref(dst);
|
|
CVPixelBufferRetain(pixelBuffer);
|
|
picture_Release(dst);
|
|
|
|
if (pixelBuffer == NULL) {
|
|
msg_Err(vd, "No pixelBuffer ref attached to pic!");
|
|
CVPixelBufferRelease(pixelBuffer);
|
|
return;
|
|
}
|
|
|
|
id aspectRatio = @{
|
|
(__bridge NSString*)kCVImageBufferPixelAspectRatioHorizontalSpacingKey:
|
|
@(vd->source->i_sar_num),
|
|
(__bridge NSString*)kCVImageBufferPixelAspectRatioVerticalSpacingKey:
|
|
@(vd->source->i_sar_den)
|
|
};
|
|
|
|
CVBufferSetAttachment(
|
|
pixelBuffer,
|
|
kCVImageBufferPixelAspectRatioKey,
|
|
(__bridge CFDictionaryRef)aspectRatio,
|
|
kCVAttachmentMode_ShouldPropagate
|
|
);
|
|
|
|
CMSampleBufferRef sampleBuffer = NULL;
|
|
CMVideoFormatDescriptionRef formatDesc = NULL;
|
|
OSStatus err = CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, &formatDesc);
|
|
if (err != noErr) {
|
|
msg_Err(vd, "Image buffer format desciption creation failed!");
|
|
CVPixelBufferRelease(pixelBuffer);
|
|
return;
|
|
}
|
|
|
|
vlc_tick_t now = vlc_tick_now();
|
|
CFTimeInterval ca_now = CACurrentMediaTime();
|
|
vlc_tick_t ca_now_ts = vlc_tick_from_sec(ca_now);
|
|
vlc_tick_t diff = date - now;
|
|
CFTimeInterval ca_date = secf_from_vlc_tick(ca_now_ts + diff);
|
|
CMSampleTimingInfo sampleTimingInfo = {
|
|
.decodeTimeStamp = kCMTimeInvalid,
|
|
.duration = kCMTimeInvalid,
|
|
.presentationTimeStamp = CMTimeMakeWithSeconds(ca_date, 1000000)
|
|
};
|
|
|
|
err = CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault, pixelBuffer, formatDesc, &sampleTimingInfo, &sampleBuffer);
|
|
CFRelease(formatDesc);
|
|
CVPixelBufferRelease(pixelBuffer);
|
|
if (err != noErr) {
|
|
msg_Err(vd, "Image buffer creation failed!");
|
|
return;
|
|
}
|
|
|
|
@synchronized(sys.displayLayer) {
|
|
[sys.displayLayer enqueueSampleBuffer:sampleBuffer];
|
|
}
|
|
|
|
CFRelease(sampleBuffer);
|
|
}
|
|
|
|
static CGRect RegionBackingFrame(VLCSampleBufferDisplay* sys,
|
|
const struct subpicture_region_rendered *r)
|
|
{
|
|
// Invert y coords for CoreGraphics
|
|
const float y = sys->place.height - r->place.height - r->place.y;
|
|
|
|
return CGRectMake(
|
|
r->place.x + sys->place.x,
|
|
y + sys->place.y,
|
|
r->place.width,
|
|
r->place.height
|
|
);
|
|
}
|
|
|
|
static void UpdateSubpictureRegions(vout_display_t *vd,
|
|
const vlc_render_subpicture *subpicture)
|
|
{
|
|
VLCSampleBufferDisplay *sys;
|
|
sys = (__bridge VLCSampleBufferDisplay*)vd->sys;
|
|
|
|
if (sys.subpicture == nil || subpicture == NULL)
|
|
return;
|
|
|
|
NSMutableArray *regions = [NSMutableArray new];
|
|
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
|
|
const struct subpicture_region_rendered *r;
|
|
vlc_vector_foreach(r, &subpicture->regions) {
|
|
CFIndex length = r->p_picture->format.i_height * r->p_picture->p->i_pitch;
|
|
const size_t pixels_offset =
|
|
r->p_picture->format.i_y_offset * r->p_picture->p->i_pitch +
|
|
r->p_picture->format.i_x_offset * r->p_picture->p->i_pixel_pitch;
|
|
|
|
CFDataRef data = CFDataCreate(
|
|
NULL,
|
|
r->p_picture->p->p_pixels + pixels_offset,
|
|
length - pixels_offset);
|
|
CGDataProviderRef provider = CGDataProviderCreateWithCFData(data);
|
|
CGImageRef image = CGImageCreate(
|
|
r->place.width, r->place.height,
|
|
8, 32, r->p_picture->p->i_pitch,
|
|
space, kCGImageAlphaFirst,
|
|
provider, NULL, true, kCGRenderingIntentDefault
|
|
);
|
|
VLCSampleBufferSubpictureRegion *region;
|
|
region = [VLCSampleBufferSubpictureRegion new];
|
|
region.subpicture = sys.subpicture;
|
|
region.image = image;
|
|
|
|
region.backingFrame = RegionBackingFrame(sys, r);
|
|
[regions addObject:region];
|
|
CGDataProviderRelease(provider);
|
|
CFRelease(data);
|
|
}
|
|
CGColorSpaceRelease(space);
|
|
|
|
sys.subpicture.regions = regions;
|
|
}
|
|
|
|
static bool IsSubpictureDrawNeeded(vout_display_t *vd, const vlc_render_subpicture *subpicture)
|
|
{
|
|
VLCSampleBufferDisplay *sys;
|
|
sys = (__bridge VLCSampleBufferDisplay*)vd->sys;
|
|
|
|
if (subpicture == NULL)
|
|
{
|
|
if (sys.subpicture == nil)
|
|
return false;
|
|
sys.subpicture = nil;
|
|
/* Need to draw one last time in order to clear the current subpicture */
|
|
return true;
|
|
}
|
|
|
|
size_t count = subpicture->regions.size;
|
|
const struct subpicture_region_rendered *r;
|
|
|
|
if (!sys.subpicture || subpicture->i_order != sys.subpicture.order)
|
|
{
|
|
/* Subpicture content is different */
|
|
sys.subpicture = [VLCSampleBufferSubpicture new];
|
|
sys.subpicture.sys = sys;
|
|
sys.subpicture.order = subpicture->i_order;
|
|
UpdateSubpictureRegions(vd, subpicture);
|
|
return true;
|
|
}
|
|
|
|
bool draw = false;
|
|
|
|
if (count == sys.subpicture.regions.count)
|
|
{
|
|
size_t i = 0;
|
|
vlc_vector_foreach(r, &subpicture->regions)
|
|
{
|
|
VLCSampleBufferSubpictureRegion *region =
|
|
sys.subpicture.regions[i++];
|
|
|
|
CGRect newRegion = RegionBackingFrame(sys, r);
|
|
|
|
if ( !CGRectEqualToRect(region.backingFrame, newRegion) )
|
|
{
|
|
/* Subpicture regions are different */
|
|
draw = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Subpicture region count is different */
|
|
draw = true;
|
|
}
|
|
|
|
if (!draw)
|
|
return false;
|
|
|
|
/* Store the current subpicture regions in order to compare then later.
|
|
*/
|
|
|
|
UpdateSubpictureRegions(vd, subpicture);
|
|
return true;
|
|
}
|
|
|
|
static void RenderSubpicture(vout_display_t *vd, const vlc_render_subpicture *spu)
|
|
{
|
|
if (!IsSubpictureDrawNeeded(vd, spu))
|
|
return;
|
|
|
|
VLCSampleBufferDisplay *sys;
|
|
sys = (__bridge VLCSampleBufferDisplay*)vd->sys;
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[sys.spuView drawSubpicture:sys.subpicture];
|
|
});
|
|
}
|
|
|
|
static void PrepareDisplay (vout_display_t *vd) {
|
|
VLCSampleBufferDisplay *sys;
|
|
sys = (__bridge VLCSampleBufferDisplay*)vd->sys;
|
|
|
|
@synchronized(sys.displayLayer) {
|
|
if (sys.displayLayer)
|
|
return;
|
|
}
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (sys.displayView)
|
|
return;
|
|
VLCSampleBufferDisplayView *displayView =
|
|
[[VLCSampleBufferDisplayView alloc] initWithVoutDisplay:vd];
|
|
VLCSampleBufferSubpictureView *spuView =
|
|
[VLCSampleBufferSubpictureView new];
|
|
id container = sys.container;
|
|
//TODO: Is it still relevant ?
|
|
if ([container respondsToSelector:@selector(addVoutSubview:)]) {
|
|
[container addVoutSubview:displayView];
|
|
[container addVoutSubview:spuView];
|
|
} else if ([container isKindOfClass:[VLCView class]]) {
|
|
VLCView *containerView = container;
|
|
[containerView addSubview:displayView];
|
|
[containerView addSubview:spuView];
|
|
[displayView setFrame:containerView.bounds];
|
|
[spuView setFrame:containerView.bounds];
|
|
} else {
|
|
displayView = nil;
|
|
spuView = nil;
|
|
}
|
|
|
|
vout_display_PlacePicture(&sys->place, vd->source, &vd->cfg->display);
|
|
|
|
sys.displayView = displayView;
|
|
sys.spuView = spuView;
|
|
@synchronized(sys.displayLayer) {
|
|
sys.displayLayer = displayView.displayLayer;
|
|
}
|
|
});
|
|
}
|
|
|
|
static void Prepare (vout_display_t *vd, picture_t *pic,
|
|
const vlc_render_subpicture *subpicture, vlc_tick_t date)
|
|
{
|
|
PrepareDisplay(vd);
|
|
if (pic) {
|
|
RenderPicture(vd, pic, date);
|
|
}
|
|
|
|
RenderSubpicture(vd, subpicture);
|
|
}
|
|
|
|
static void Display(vout_display_t *vd, picture_t *pic)
|
|
{
|
|
}
|
|
|
|
static int Control (vout_display_t *vd, int query)
|
|
{
|
|
VLCSampleBufferDisplay *sys;
|
|
sys = (__bridge VLCSampleBufferDisplay*)vd->sys;
|
|
|
|
switch (query)
|
|
{
|
|
case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
|
|
case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
|
|
case VOUT_DISPLAY_CHANGE_ZOOM:
|
|
case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
|
|
case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
|
|
{
|
|
vout_display_PlacePicture(
|
|
&sys->place, vd->source, &vd->cfg->display);
|
|
break;
|
|
}
|
|
default:
|
|
msg_Err (vd, "Unhandled request %d", query);
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static int Open (vout_display_t *vd,
|
|
video_format_t *fmt, vlc_video_context *context)
|
|
{
|
|
// Display isn't compatible with 360 content hence opening with this kind
|
|
// of projection should fail if display use isn't forced
|
|
if (!vd->obj.force && fmt->projection_mode != PROJECTION_MODE_RECTANGULAR) {
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
if (vd->cfg->window->type != VLC_WINDOW_TYPE_NSOBJECT)
|
|
return VLC_EGENERIC;
|
|
|
|
VLCSampleBufferDisplay *sys = [VLCSampleBufferDisplay new];
|
|
if (sys == nil) {
|
|
return VLC_ENOMEM;
|
|
}
|
|
|
|
// Display will only work with CVPX video context
|
|
filter_t *converter = NULL;
|
|
if (!vlc_video_context_GetPrivate(context, VLC_VIDEO_CONTEXT_CVPX)) {
|
|
converter = CreateCVPXConverter(vd);
|
|
if (!converter)
|
|
return VLC_EGENERIC;
|
|
}
|
|
sys->converter = converter;
|
|
|
|
@autoreleasepool {
|
|
id container = (__bridge id)vd->cfg->window->handle.nsobject;
|
|
if (!container) {
|
|
msg_Err(vd, "No drawable-nsobject found!");
|
|
DeleteCVPXConverter(converter);
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
sys.container = container;
|
|
|
|
vd->sys = (__bridge_retained void*)sys;
|
|
|
|
static const struct vlc_display_operations ops = {
|
|
Close, Prepare, Display, Control, NULL, NULL, NULL,
|
|
};
|
|
|
|
vd->ops = &ops;
|
|
|
|
static const vlc_fourcc_t subfmts[] = {
|
|
VLC_CODEC_ARGB,
|
|
0
|
|
};
|
|
|
|
vd->info.subpicture_chromas = subfmts;
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Module descriptor
|
|
*/
|
|
vlc_module_begin()
|
|
set_description(N_("CoreMedia sample buffers based video output display"))
|
|
set_subcategory(SUBCAT_VIDEO_VOUT)
|
|
set_callback_display(Open, 600)
|
|
vlc_module_end()
|