• Metal渲染:实现画面比例功能


    如果我们使用AVPlayer及AVPlayerLayer进行视频播放的话,那们我们可以使用AVPlayerLayer.videoGravity来控件画面的显示比例(Resize, ResizeAspect, ResizeAspectFill)。但是如果我们使用Metal进行视频渲染的放要如何实现画面比例呢?

    其实我们可以通过设置Metal的View Point来实现:

    画面比例如果使用AVSampleBufferDisplayLayer有一个videoGravity属性:

    @property(copy) AVLayerVideoGravity videoGravity;
    

    使用系统播放器时,AVPlayerLayer也有一个videoGravity属性:

    @property(copy) AVLayerVideoGravity videoGravity;
    

    但在使用Metal进行渲染时MTLLayer并没有类似的属性,只有在父类CALayer有一个contentsGravity属性:

    @property(copy) CALayerContentsGravity contentsGravity;
    

     但CALayer的contentsGravity属性并不能让MTLLayer上渲染的视频产生画面比例的效果。我们需要使用MTLRenderCommandEncoder的以下设置View point的接口来实现:

    - (void)setViewport:(MTLViewport)viewport;
    

     MTLViewport定义:

    typedef struct {
        double originX, originY, width, height, znear, zfar;
    } MTLViewport;
    

     Metal是可以进行3D渲染的,在我们的视频渲染中只使用到了2D,因此这个znear和zfar直接取-1和1就好,对于2D渲染,默认MTViewpoint为:

    (MTLViewport){0.0, 0.0, drawableSize.width, drawableSize.height, -1.0, 1.0 }];
    

     以上view point会使视频纹理缩放填满整个画布,而不保持视频原分辨率比例 。

    在我们下面通过设置view point方法来实现画面比例前,有如下条件:

    // 视频宽高
    CGFloat videoWidth = textureSize.width;
    CGFloat videoHeight = textureSize.height;
    // 视频高宽比
    CGFloat videoScale = videoHeight/videoWidth;
    
    // 画面同宽比
    CGFloat canvaseScale = (CGFloat)(drawableSize.height) / (CGFloat)(drawableSize.width);
    
    // 视频实际显示的宽高,即在view point中使用的宽高
    CGFloat videoDisplayWidth = drawableSize.width;
    CGFloat videoDisplayHeight = drawableSize.height;
    

     视频的画面比例通常我们需要实现三种情况:


    kCAGravityResize

    视频宽高随画布宽高进行缩放,将画布填满,不考虑视频本身的比例,图像会变形。

    Resize的实现很简单,不用特殊设置ViewPoint默认视频就是按Layer进行拉伸变形填满整个Metal Layer的。由于要实现其它两种画面比例,设置viewPoint如下:

    [encoder setViewport:(MTLViewport){0.0, 0.0, videoDisplayWidth, videoDisplayHeight, -1.0, 1.0 }];
    

    kCAGravityResizeAspect

    将视频进行等比缩放,当视频相对画布较大的边刚好和画布一样时不再缩放,如果视频比例与画面比例不相等时,上下或者左右会出现黑边。

    分两种情况。

    1. 如果canvaseScale > videoScale

    即画布相比例对视频比例,更高,那么当视频等比缩放到和画布的宽度一样时,画面上下就会有黑边,我们使上下的黑边一样,如图:

    1578741812_10_w686_h540.png

    视频显示宽高分别为:

    videoDisplayWidth = drawableSize.width;
    videoDisplayHeight = drawableSize.width * (videoHeight/videoWidth);

    此时的高度没有画布高,MTLViewport的originX为0,originY应该比0大,视频向上偏移,使下面有黑边,偏移的量正好是画布高度减去缩放后视频显示高度的差再除以2,因此viewPoint设置如下:

    [encoder setViewport:(MTLViewport){0.0, (drawableSize.height - videoDisplayHeight)/2, videoDisplayWidth, videoDisplayHeight, -1.0, 1.0 }];
    

    2. 如果canvaseScale <= videoScale

    即画布相对视频比例,更宽,那么当视频等比缩放到与画布高度一样时,画面的左右就会有黑边,我们使左右黑边一样,如图:

    1578741829_85_w710_h446.png

    视频显示宽高分别为:

    videoDisplayWidth = drawableSize.height * (videoWidth/videoHeight);
    videoDisplayHeight = drawableSize.height;

    此时的宽没有画布宽,MTLViewpoint的originY为0,orignX应该比0大,视频向右偏移,使左边有黑边,偏移的量正好是画布宽减去缩放后视频显示宽的差再除以2,因此viewPoint设置如下:

    [encoder setViewport:(MTLViewport){(drawableSize.width - videoDisplayWidth)/2, 0, videoDisplayWidth, videoDisplayHeight, -1.0, 1.0 }];

     

    kCAGravityResizeAspectFill

    将视频在kCAGravityResizeAspect的基础上进行等比放大,当上下或者左右的黑边刚好消失时停止。这样上下或者左右的画面会有一部分在画布外面看不见。

    同样分两种情况讨论。

    3. 如果canvaseScale > videoScale

    即画布相对视频比例,更高,那么当视频等比缩放到和画布的高一样时,视频的左右就会有一部分跑到画布外面去,我们使视频居中,左右跑出画布的视频宽度一样,如图:

    1578741792_94_w658_h458.png

    视频显示宽高分别为:

    videoDisplayWidth = (videoWidth/videoHeight) * drawableSize.height;
    videoDisplayHeight = drawableSize.height;

    此时视频的宽videoDisplayWidth已经比画布的宽drawableSize.width要大了,为了居中,MTLViewpoint的orignX应该比0小,使视频画面向画布左偏移移。因此viewPoint设置如下:

    [encoder setViewport:(MTLViewport){(drawableSize.width - videoDisplayWidth)/2, 0, videoDisplayWidth, videoDisplayHeight, -1.0, 1.0 }];

     

    4. 如果canvaseScale <= videoScale

    即画布相对视频比例,更宽,那么当视频等比缩放到和画面一样宽时,视频的上下就会有一部分跑到画布的外面去,我们要使视频居中,上下移出画布的视频高度应该一样,如下图:

    1578741755_88_w696_h486.png

    视频显示宽高分别为:

    videoDisplayWidth = drawableSize.width;
    videoDisplayHeight = (videoHeight/videoWidth)*drawableSize.width;

    此时视频的高videoDisplayHeitht已经比画面的高drawableSize.height要大了,为了居中,MTLViewpoint的originY应该比0小,使视频的画面向画布下偏移。因此viewPoint设置如下:

    [encoder setViewport:(MTLViewport){0, (drawableSize.height - videoDisplayHeight)/2, videoDisplayWidth, videoDisplayHeight, -1.0, 1.0 }];
  • 相关阅读:
    Git学习(一)——熟悉git操作流程
    DRF+Vue项目(一)——项目架构
    DRF框架(九)——drf偏移分页组件、drf游标分页组件(了解)、自定义过滤器、过滤器插件django-filter
    DRF框架(八)——drf-jwt手动签发与校验、搜索过滤组件、排序过滤组件、基础分页组件
    DRF框架(七) ——三大认证组件之频率组件、jwt认证
    DRF框架(六)——三大认证组件之认证组件、权限组件
    数据类型
    表操作
    库操作
    MySQL服务管理
  • 原文地址:https://www.cnblogs.com/csutanyu/p/12781886.html
Copyright © 2020-2023  润新知