[vlc-commits] [Git][videolan/vlc][master] vout: new native display renderer for Apple platforms

Felix Paul Kühne (@fkuehne) gitlab at videolan.org
Sat Sep 16 14:56:43 UTC 2023



Felix Paul Kühne pushed to branch master at VideoLAN / VLC


Commits:
629d4a34 by Maxime Chapelet at 2023-09-16T14:31:56+00:00
vout: new native display renderer for Apple platforms

This new module provides the ability to render Core Video pixel buffers pictures into an AVSampleBufferDisplayLayer.
Pictures decoded by a software decoder are converted to Core Video pixel buffers and picture decoded by the VideoToolbox decoder are rendered directly.
With this native API, rendering will be able to handle HDR tone-mapping natively without using OpenGL/libplacebo as it will be backed by Metal.
It also opens the ability to handle Picture in Picture mode on supported Darwin platforms with upcoming additional work.

It currently have the limitation to not support spherical video as it doesn't seems to be feasible at this time.

This module has the highest priority and will be the first vout display to be opened.

- - - - -


5 changed files:

- modules/gui/macosx/windows/video/VLCVoutView.m
- modules/meson.build
- modules/video_output/Makefile.am
- + modules/video_output/apple/VLCSampleBufferDisplay.m
- modules/video_output/apple/meson.build


Changes:

=====================================
modules/gui/macosx/windows/video/VLCVoutView.m
=====================================
@@ -107,7 +107,9 @@
 
 - (void)layout {
     NSRect bounds = [self convertRectToBacking:self.bounds];
-    dispatch_sync(_eventQueue, ^{
+    // dispatch the event async to prevent potential deadlock 
+    // with video output's RenderPicture's display lock
+    dispatch_async(_eventQueue, ^{
         if (_wnd == NULL)
             return;
         vlc_window_ReportSize(_wnd, bounds.size.width, bounds.size.height);


=====================================
modules/meson.build
=====================================
@@ -90,6 +90,7 @@ if host_system == 'darwin'
     coreaudio_dep = dependency('CoreAudio', required: true)
     coreimage_dep = dependency('CoreImage', required: true)
     coregraphics_dep = dependency('CoreGraphics', required: true)
+    avkit_dep = dependency('AVKit', required: true)
 endif
 
 # macOS specific dependencies


=====================================
modules/video_output/Makefile.am
=====================================
@@ -74,6 +74,19 @@ libcaopengllayer_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)' \
 vout_LTLIBRARIES += libvout_macosx_plugin.la libcaopengllayer_plugin.la libwindow_macosx_plugin.la
 endif
 
+if HAVE_DARWIN
+libsamplebufferdisplay_plugin_la_SOURCES = video_output/apple/VLCSampleBufferDisplay.m codec/vt_utils.c codec/vt_utils.h
+libsamplebufferdisplay_plugin_la_OBJCFLAGS = $(AM_OBJCFLAGS) -fobjc-arc
+if HAVE_OSX
+libsamplebufferdisplay_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)' \
+	-Wl,-framework,AVFoundation,-framework,AVKit,-framework,Cocoa,-framework,CoreMedia,-framework,QuartzCore
+else
+libsamplebufferdisplay_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)' \
+	-Wl,-framework,AVFoundation,-framework,AVKit,-framework,UIKit,-framework,CoreMedia,-framework,QuartzCore
+endif
+vout_LTLIBRARIES += libsamplebufferdisplay_plugin.la
+endif
+
 libvout_ios_plugin_la_SOURCES = video_output/opengl/display.c $(OPENGL_VOUT_COMMONSOURCES)
 libvout_ios_plugin_la_CFLAGS = $(AM_CFLAGS) $(OPENGL_COMMONCFLAGS) -DUSE_OPENGL_ES2
 libvout_ios_plugin_la_LIBADD = libvlc_opengles.la


=====================================
modules/video_output/apple/VLCSampleBufferDisplay.m
=====================================
@@ -0,0 +1,693 @@
+/*****************************************************************************
+ * 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"
+
+/**
+ * Protocol declaration that drawable-nsobject should follow
+ */
+ at protocol VLCOpenGLVideoViewEmbedding <NSObject>
+- (void)addVoutSubview:(VLCView *)view;
+- (void)removeVoutSubview:(VLCView *)view;
+ at end
+
+#pragma mark -
+ at class VLCSampleBufferSubpicture, VLCSampleBufferDisplay;
+
+ at interface VLCSampleBufferSubpictureRegion: NSObject
+ at property (nonatomic, weak) VLCSampleBufferSubpicture *subpicture;   
+ at property (nonatomic) CGRect backingFrame;
+ at property (nonatomic) CGImageRef image;
+ at end
+
+ at implementation VLCSampleBufferSubpictureRegion
+- (void)dealloc {
+    CGImageRelease(_image);
+}
+ at end
+
+#pragma mark -
+
+ at interface VLCSampleBufferSubpicture: NSObject
+ at property (nonatomic, weak) VLCSampleBufferDisplay *sys;
+ at property (nonatomic) NSArray<VLCSampleBufferSubpictureRegion *> *regions;
+ at property (nonatomic) int64_t order;
+ at end
+
+ at implementation VLCSampleBufferSubpicture
+
+ at end
+
+#pragma mark -
+
+ at interface VLCSampleBufferSubpictureView: VLCView
+- (void)drawSubpicture:(VLCSampleBufferSubpicture *)subpicture;
+ at end
+
+ at 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
+}
+
+ at end
+
+#pragma mark -
+
+ at interface VLCSampleBufferDisplayView: VLCView <CALayerDelegate>
+ at property (nonatomic, readonly) vout_display_t *vd;
+- (instancetype)initWithVoutDisplay:(vout_display_t *)vd;
+- (AVSampleBufferDisplayLayer *)displayLayer;
+ at end
+
+ at 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;
+}
+
+ at end
+
+#pragma mark -
+
+ at 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;
+ at end
+
+ at implementation VLCSampleBufferDisplay
+ at end
+
+#pragma mark -
+#pragma mark Module functions
+
+static void Close(vout_display_t *vd)
+{
+    VLCSampleBufferDisplay *sys;
+    sys = (__bridge_transfer VLCSampleBufferDisplay*)vd->sys;
+
+    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, 
+                                 subpicture_t *subpicture,
+                                 subpicture_region_t *r)
+{
+    const float scale_w = (float)(sys->place.width)  / subpicture->i_original_picture_width;
+    const float scale_h = (float)(sys->place.height) / subpicture->i_original_picture_height;
+
+    // Invert y coords for CoreGraphics
+    const float y = subpicture->i_original_picture_height - r->fmt.i_visible_height - r->i_y;
+
+    return CGRectMake(
+        scale_w * r->i_x + sys->place.x, 
+        scale_h * y + sys->place.y,
+        scale_w * r->fmt.i_visible_width, 
+        scale_h * r->fmt.i_visible_height
+    );
+}
+
+static void UpdateSubpictureRegions(vout_display_t *vd, 
+                                    subpicture_t *subpicture) 
+{
+    VLCSampleBufferDisplay *sys;
+    sys = (__bridge VLCSampleBufferDisplay*)vd->sys;
+
+    if (sys.subpicture == nil || subpicture == NULL)
+        return;
+    
+    NSMutableArray *regions = [NSMutableArray new];
+    CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
+    for (subpicture_region_t *r = subpicture->p_region; r; r = r->p_next) {
+        CFIndex length = r->fmt.i_height * r->p_picture->p->i_pitch;
+        const size_t pixels_offset =
+                r->fmt.i_y_offset * r->p_picture->p->i_pitch +
+                r->fmt.i_x_offset * r->p_picture->p->i_pixel_pitch;
+        r->p_picture->p->i_visible_pitch = 
+            r->fmt.i_visible_width * 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->fmt.i_visible_width, r->fmt.i_visible_height, 
+            8, 32, r->p_picture->p->i_pitch,  
+            space, kCGImageByteOrderDefault | kCGImageAlphaFirst,
+            provider, NULL, true, kCGRenderingIntentDefault
+            );
+        VLCSampleBufferSubpictureRegion *region;
+        region = [VLCSampleBufferSubpictureRegion new];
+        region.subpicture = sys.subpicture;
+        region.image = image;
+
+        region.backingFrame = RegionBackingFrame(sys, subpicture, r);
+        [regions addObject:region];
+        CGDataProviderRelease(provider);
+        CFRelease(data);
+    }
+    CGColorSpaceRelease(space);
+
+    sys.subpicture.regions = regions;
+}
+
+static bool IsSubpictureDrawNeeded(vout_display_t *vd, subpicture_t *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 = 0;
+    for (subpicture_region_t *r = subpicture->p_region;
+         r != NULL; r = r->p_next)
+        count++;
+
+    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;
+        for (subpicture_region_t *r = subpicture->p_region;
+             r != NULL; r = r->p_next)
+        {
+            VLCSampleBufferSubpictureRegion *region =
+                sys.subpicture.regions[i++];
+
+            CGRect newRegion = RegionBackingFrame(sys, subpicture, 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, subpicture_t *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, 
+                           subpicture_t *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 vlc_decoder_device * CVPXHoldDecoderDevice(vlc_object_t *o, void *sys)
+{
+    VLC_UNUSED(o);
+    vout_display_t *vd = sys;
+    static vlc_decoder_device *device = NULL;
+    if (!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 *
+SW_to_CVPX_converter_Create(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 DeleteFilter( filter_t * p_filter )
+{
+    if (!p_filter)
+        return;
+
+    if( p_filter->p_module )
+    {
+        filter_Close( p_filter );
+        module_unneed( p_filter, p_filter->p_module );
+    }
+
+    es_format_Clean( &p_filter->fmt_in );
+    es_format_Clean( &p_filter->fmt_out );
+
+    vlc_object_delete(p_filter);
+}
+
+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 = SW_to_CVPX_converter_Create(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!");
+            DeleteFilter(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()


=====================================
modules/video_output/apple/meson.build
=====================================
@@ -20,6 +20,17 @@ vlc_modules += {
     'dependencies' : [foundation_dep, corefoundation_dep, corevideo_dep, darwingl_dep],
 }
 
+vlc_modules += {
+    'name' : 'samplebufferdisplay',
+    'sources' : files(
+        'VLCSampleBufferDisplay.m',
+        '../../codec/vt_utils.c',
+        '../../codec/vt_utils.h'
+    ),
+    'objc_args' : ['-fobjc-arc'],
+    'dependencies' : [avfoundation_dep, avkit_dep, cocoa_dep, coremedia_dep, quartz_dep],
+}
+
 if have_ios or have_tvos
     vlc_modules += {
         'name' : 'caeagl',



View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/629d4a343045b425cf98962c588909438a85728d

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/629d4a343045b425cf98962c588909438a85728d
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list