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 }];

 

posted on 2020-04-26 19:57  课蜜黄蜂  阅读(774)  评论(0编辑  收藏  举报