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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM