案例效果圖如下:

視頻渲染.gif
案例流程如下:
- 使用自定義的
CCAssetReader工具類,讀取mov/mp4視頻文件 - Metal渲染回調(diào) 還原成
CMSampleBufferRef圖像數(shù)據(jù),然后將讀取到CVPixelBufferRef視頻像素緩存區(qū) - 通過
CoreVideo獲取Y紋理,UV紋理 - 在自定義著色器將顏色編碼格式由YUV轉(zhuǎn)換為RGB,顯示到屏幕上
CCAssetReader
CCAssetReader的功能與AVAssetReader與類似。
AVAssetReader功能
- 直接從存儲(chǔ)中讀取原始未解碼的媒體樣本,獲取解碼為可渲染形式的樣本。
- 混合資產(chǎn)的多個(gè)?軌,并使?和組合多個(gè)視頻軌道
流程圖如下:

AVAssetReader.png
CCAssetReader代碼
- CCAssetReader.h
//
// CCAssetReader.h
// 002--MetalRenderMOV
//
// Created by CC老師 on 2019/5/7.
// Copyright ? 2019年 CC老師. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
@interface CCAssetReader : NSObject
//初始化
- (instancetype)initWithUrl:(NSURL *)url;
//從MOV文件讀取CMSampleBufferRef 數(shù)據(jù)
- (CMSampleBufferRef)readBuffer;
@end
- CCAssetReader.m
//
// CCAssetReader.m
// 002--MetalRenderMOV
//
// Created by CC老師 on 2019/5/7.
// Copyright ? 2019年 CC老師. All rights reserved.
//
#import "CCAssetReader.h"
@implementation CCAssetReader
{
//軌道
AVAssetReaderTrackOutput *readerVideoTrackOutput;
//AVAssetReader可以從原始數(shù)據(jù)里獲取解碼后的音視頻數(shù)據(jù)
AVAssetReader *assetReader;
//視頻地址
NSURL *videoUrl;
//鎖
NSLock *lock;
}
//初始化
- (instancetype)initWithUrl:(NSURL *)url{
self = [super init];
if(self != nil)
{
videoUrl = url;
lock = [[NSLock alloc]init];
[self setUpAsset];
}
return self;
}
//Asset 相關(guān)設(shè)置
-(void)setUpAsset{
//AVURLAssetPreferPreciseDurationAndTimingKey 默認(rèn)為NO,YES表示提供精確的時(shí)長
NSDictionary *inputOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:AVURLAssetPreferPreciseDurationAndTimingKey];
//1. 創(chuàng)建AVURLAsset 是AVAsset 子類,用于從本地/遠(yuǎn)程URL初始化資源
AVURLAsset *inputAsset = [[AVURLAsset alloc] initWithURL:videoUrl options:inputOptions];
//2.異步加載資源
//weakSelf 解決循環(huán)引用
__weak typeof(self) weakSelf = self;
//定義屬性名稱
NSString *tracks = @"tracks";
//對(duì)資源所需的鍵執(zhí)行標(biāo)準(zhǔn)的異步載入操作,這樣就可以訪問資源的tracks屬性時(shí),就不會(huì)受到阻礙.
[inputAsset loadValuesAsynchronouslyForKeys:@[tracks] completionHandler: ^{
//延長self 生命周期
__strong typeof(self) strongSelf = weakSelf;
//開辟子線程并發(fā)隊(duì)列異步函數(shù)來處理讀取的inputAsset
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
//獲取狀態(tài)碼.
AVKeyValueStatus tracksStatus = [inputAsset statusOfValueForKey:@"tracks" error:&error];
//如果狀態(tài)不等于成功加載,則返回并打印錯(cuò)誤信息
if (tracksStatus != AVKeyValueStatusLoaded)
{
NSLog(@"error %@", error);
return;
}
//處理讀取的inputAsset
[weakSelf processWithAsset:inputAsset];
});
}];
}
//處理獲取到的asset
- (void)processWithAsset:(AVAsset *)asset
{
//鎖定
[lock lock];
NSLog(@"processWithAsset");
NSError *error = nil;
//1.創(chuàng)建AVAssetReader
assetReader = [AVAssetReader assetReaderWithAsset:asset error:&error];
//2.kCVPixelBufferPixelFormatTypeKey 像素格式.
/*
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange : 420v
kCVPixelFormatType_32BGRA : iOS在內(nèi)部進(jìn)行YUV至BGRA格式轉(zhuǎn)換
*/
NSMutableDictionary *outputSettings = [NSMutableDictionary dictionary];
[outputSettings setObject:@(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) forKey:(id)kCVPixelBufferPixelFormatTypeKey];
/*3. 設(shè)置readerVideoTrackOutput
assetReaderTrackOutputWithTrack:(AVAssetTrack *)track outputSettings:(nullable NSDictionary<NSString *, id> *)outputSettings
參數(shù)1: 表示讀取資源中什么信息
參數(shù)2: 視頻參數(shù)
*/
readerVideoTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] outputSettings:outputSettings];
//alwaysCopiesSampleData : 表示緩存區(qū)的數(shù)據(jù)輸出之前是否會(huì)被復(fù)制.YES:輸出總是從緩存區(qū)提供復(fù)制的數(shù)據(jù),你可以自由的修改這些緩存區(qū)數(shù)據(jù)
readerVideoTrackOutput.alwaysCopiesSampleData = NO;
//4.為assetReader 填充輸出
[assetReader addOutput:readerVideoTrackOutput];
//5.assetReader 開始讀取.并且判斷是否開始.
if ([assetReader startReading] == NO)
{
NSLog(@"Error reading from file at URL: %@", asset);
}
//取消鎖
[lock unlock];
}
//讀取Buffer 數(shù)據(jù)
- (CMSampleBufferRef)readBuffer {
//鎖定
[lock lock];
CMSampleBufferRef sampleBufferRef = nil;
//1.判斷readerVideoTrackOutput 是否創(chuàng)建成功.
if (readerVideoTrackOutput) {
//復(fù)制下一個(gè)緩存區(qū)的內(nèi)容到sampleBufferRef
sampleBufferRef = [readerVideoTrackOutput copyNextSampleBuffer];
}
//2.判斷assetReader 并且status 是已經(jīng)完成讀取 則重新清空readerVideoTrackOutput/assetReader.并重新初始化它們
if (assetReader && assetReader.status == AVAssetReaderStatusCompleted) {
NSLog(@"customInit");
readerVideoTrackOutput = nil;
assetReader = nil;
[self setUpAsset];
}
//取消鎖
[lock unlock];
//3.返回讀取到的sampleBufferRef 數(shù)據(jù)
return sampleBufferRef;
}
@end
LeoShaderTypes.h
- 定義一個(gè)結(jié)構(gòu)體,存儲(chǔ)頂點(diǎn)結(jié)構(gòu)數(shù)據(jù)
typedef struct
{
//頂點(diǎn)坐標(biāo)(x,y,z,w)
vector_float4 position;
//紋理坐標(biāo)(s,t)
vector_float2 textureCoordinate;
} LeoVertex;
- 設(shè)置一個(gè)轉(zhuǎn)換矩陣機(jī)構(gòu)體,從YUV轉(zhuǎn)換到RGB
//轉(zhuǎn)換矩陣 YUV - RGB轉(zhuǎn)換矩陣結(jié)構(gòu)
typedef struct {
//三維矩陣
matrix_float3x3 matrix;
//偏移量
vector_float3 offset;
} LeoConvertMatrix;
- 頂點(diǎn)索引數(shù)據(jù)
//頂點(diǎn)函數(shù)輸入索引
typedef enum LeoVertexInputIndex
{
LeoVertexInputIndexVertices = 0,
} LeoVertexInputIndex;
- 片元函數(shù)緩存區(qū)索引
//片元函數(shù)緩存區(qū)索引
typedef enum LeoFragmentBufferIndex
{
LeoFragmentInputIndexMatrix = 0,
} LeoFragmentBufferIndex;
- 片元函數(shù)紋理索引
//片元函數(shù)紋理索引
typedef enum LeoFragmentTextureIndex
{
//Y紋理
LeoFragmentTextureIndexTextureY = 0,
//UV紋理
LeoFragmentTextureIndexTextureUV = 1,
} LeoFragmentTextureIndex;
LeoShaders.metal
- 定義一個(gè)結(jié)構(gòu)體用來存放從頂點(diǎn)著色器到片元著色器的數(shù)據(jù)
//結(jié)構(gòu)體(用于頂點(diǎn)函數(shù)輸出/片元函數(shù)輸入)
typedef struct{
float4 clipSpacePosition [[position]]; // position的修飾符表示這個(gè)是頂點(diǎn)
float2 textureCoordinate; // 紋理坐標(biāo)
} RasterizerData;
- 頂點(diǎn)著色器函數(shù),這個(gè)項(xiàng)目中,我們只需要將頂點(diǎn)坐標(biāo)和紋理坐標(biāo)傳遞到片元著色器
//RasterizerData 返回?cái)?shù)據(jù)類型->片元函數(shù)
// vertex_id是頂點(diǎn)shader每次處理的index,用于定位當(dāng)前的頂點(diǎn)
// buffer表明是緩存數(shù)據(jù),0是索引
vertex RasterizerData
vertexShader(uint vertexID [[ vertex_id ]],
constant LeoVertex *vertexArray [[buffer(LeoVertexInputIndexVertices)]])
{
RasterizerData out;
//頂點(diǎn)坐標(biāo)
out.clipSpacePosition = vertexArray[vertexID].position;
//紋理坐標(biāo)
out.textureCoordinate = vertexArray[vertexID].textureCoordinate;
return out;
}
- 片元著色器的任務(wù)就是講YUV轉(zhuǎn)換成RGB
// stage_in表示這個(gè)數(shù)據(jù)來自光柵化。(光柵化是頂點(diǎn)處理之后的步驟,業(yè)務(wù)層無法修改)
// texture表明是紋理數(shù)據(jù),CCFragmentTextureIndexTextureY是索引
// texture表明是紋理數(shù)據(jù),CCFragmentTextureIndexTextureUV是索引
// buffer表明是緩存數(shù)據(jù), CCFragmentInputIndexMatrix是索引
fragment float4
samplingShader(RasterizerData input [[stage_in]],
texture2d<float> textureY [[ texture(LeoFragmentTextureIndexTextureY) ]],
texture2d<float> textureUV [[ texture(LeoFragmentTextureIndexTextureUV) ]],
constant LeoConvertMatrix *convertMatrix [[ buffer(LeoFragmentInputIndexMatrix) ]])
{
//1.獲取紋理采樣器
constexpr sampler textureSampler (mag_filter::linear,
min_filter::linear);
/*
2. 讀取YUV 顏色值
textureY.sample(textureSampler, input.textureCoordinate).r
從textureY中的紋理采集器中讀取,紋理坐標(biāo)對(duì)應(yīng)上的R值.(Y)
textureUV.sample(textureSampler, input.textureCoordinate).rg
從textureUV中的紋理采集器中讀取,紋理坐標(biāo)對(duì)應(yīng)上的RG值.(UV)
*/
float3 yuv = float3(textureY.sample(textureSampler, input.textureCoordinate).r,
textureUV.sample(textureSampler, input.textureCoordinate).rg);
//3.將YUV 轉(zhuǎn)化為 RGB值.convertMatrix->matrix * (YUV + convertMatrix->offset)
float3 rgb = convertMatrix->matrix * (yuv + convertMatrix->offset);
//4.返回顏色值(RGBA)
return float4(rgb, 1.0);
}
ViewController
- 初始化
MTKView
-(void)setupMTKView{
//1.初始化mtkView
self.mtkView = [[MTKView alloc] initWithFrame:self.view.bounds];
// 獲取默認(rèn)的device
self.mtkView.device = MTLCreateSystemDefaultDevice();
//設(shè)置self.view = self.mtkView;
self.view = self.mtkView;
//設(shè)置代理
self.mtkView.delegate = self;
//獲取視口size
self.viewportSize = (vector_uint2){self.mtkView.drawableSize.width, self.mtkView.drawableSize.height};
}
- 設(shè)置
CCAssetReader,同時(shí)創(chuàng)建紋理緩存CVMetalTextureCacheRef
//2.CCAssetReader設(shè)置
-(void)setupCCAsset{
//注意CCAssetReader 支持MOV/MP4文件都可以
//1.視頻文件路徑
//NSURL *url = [[NSBundle mainBundle] URLForResource:@"kun" withExtension:@"mov"];
NSURL *url = [[NSBundle mainBundle] URLForResource:@"kun2" withExtension:@"mp4"];
//2.初始化CCAssetReader
self.reader = [[CCAssetReader alloc] initWithUrl:url];
//3._textureCache的創(chuàng)建(通過CoreVideo提供給CPU/GPU高速緩存通道讀取紋理數(shù)據(jù))
CVMetalTextureCacheCreate(NULL, NULL, self.mtkView.device, NULL, &_textureCache);
}
- 設(shè)置渲染管道
// 設(shè)置渲染管道
-(void)setupPipeline {
//1 獲取.metal
/*
newDefaultLibrary: 默認(rèn)一個(gè)metal 文件時(shí),推薦使用
newLibraryWithFile:error: 從Library 指定讀取metal 文件
newLibraryWithData:error: 從Data 中獲取metal 文件
*/
id<MTLLibrary> defaultLibrary = [self.mtkView.device newDefaultLibrary];
// 頂點(diǎn)shader,vertexShader是函數(shù)名
id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
// 片元shader,samplingShader是函數(shù)名
id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"samplingShader"];
//2.渲染管道描述信息類
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
//設(shè)置vertexFunction
pipelineStateDescriptor.vertexFunction = vertexFunction;
//設(shè)置fragmentFunction
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
// 設(shè)置顏色格式
pipelineStateDescriptor.colorAttachments[0].pixelFormat = self.mtkView.colorPixelFormat;
//3.初始化渲染管道根據(jù)渲染管道描述信息
// 創(chuàng)建圖形渲染管道,耗性能操作不宜頻繁調(diào)用
self.pipelineState = [self.mtkView.device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
error:NULL];
//4.CommandQueue是渲染指令隊(duì)列,保證渲染指令有序地提交到GPU
self.commandQueue = [self.mtkView.device newCommandQueue];
}
- 設(shè)置頂點(diǎn)
// 設(shè)置頂點(diǎn)
- (void)setupVertex {
//1.頂點(diǎn)坐標(biāo)(x,y,z,w);紋理坐標(biāo)(x,y)
//注意: 為了讓視頻全屏鋪滿,所以頂點(diǎn)大小均設(shè)置[-1,1]
static const LeoVertex quadVertices[] =
{ // 頂點(diǎn)坐標(biāo),分別是x、y、z、w; 紋理坐標(biāo),x、y;
{ { 1.0, -1.0, 0.0, 1.0 }, { 1.f, 1.f } },
{ { -1.0, -1.0, 0.0, 1.0 }, { 0.f, 1.f } },
{ { -1.0, 1.0, 0.0, 1.0 }, { 0.f, 0.f } },
{ { 1.0, -1.0, 0.0, 1.0 }, { 1.f, 1.f } },
{ { -1.0, 1.0, 0.0, 1.0 }, { 0.f, 0.f } },
{ { 1.0, 1.0, 0.0, 1.0 }, { 1.f, 0.f } },
};
//2.創(chuàng)建頂點(diǎn)緩存區(qū)
self.vertices = [self.mtkView.device newBufferWithBytes:quadVertices
length:sizeof(quadVertices)
options:MTLResourceStorageModeShared];
//3.計(jì)算頂點(diǎn)個(gè)數(shù)
self.numVertices = sizeof(quadVertices) / sizeof(LeoVertex);
}
- 設(shè)置轉(zhuǎn)換矩陣,在Metal中可以將
RGB轉(zhuǎn)換成YUV
// 設(shè)置YUV->RGB轉(zhuǎn)換的矩陣
- (void)setupMatrix {
//1.轉(zhuǎn)化矩陣
// BT.601, which is the standard for SDTV.
matrix_float3x3 kColorConversion601DefaultMatrix = (matrix_float3x3){
(simd_float3){1.164, 1.164, 1.164},
(simd_float3){0.0, -0.392, 2.017},
(simd_float3){1.596, -0.813, 0.0},
};
// BT.601 full range
matrix_float3x3 kColorConversion601FullRangeMatrix = (matrix_float3x3){
(simd_float3){1.0, 1.0, 1.0},
(simd_float3){0.0, -0.343, 1.765},
(simd_float3){1.4, -0.711, 0.0},
};
// BT.709, which is the standard for HDTV.
matrix_float3x3 kColorConversion709DefaultMatrix[] = {
(simd_float3){1.164, 1.164, 1.164},
(simd_float3){0.0, -0.213, 2.112},
(simd_float3){1.793, -0.533, 0.0},
};
//2.偏移量
vector_float3 kColorConversion601FullRangeOffset = (vector_float3){ -(16.0/255.0), -0.5, -0.5};
//3.創(chuàng)建轉(zhuǎn)化矩陣結(jié)構(gòu)體.
LeoConvertMatrix matrix;
//設(shè)置轉(zhuǎn)化矩陣
/*
kColorConversion601DefaultMatrix;
kColorConversion601FullRangeMatrix;
kColorConversion709DefaultMatrix;
*/
matrix.matrix = kColorConversion601FullRangeMatrix;
//設(shè)置offset偏移量
matrix.offset = kColorConversion601FullRangeOffset;
//4.創(chuàng)建轉(zhuǎn)換矩陣緩存區(qū).
self.convertMatrix = [self.mtkView.device newBufferWithBytes:&matrix
length:sizeof(LeoConvertMatrix)
options:MTLResourceStorageModeShared];
}
- 執(zhí)行
drawableSizeWillChange
//當(dāng)MTKView size 改變則修改self.viewportSize
- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
//設(shè)置視口
self.viewportSize = (vector_uint2){size.width, size.height};
}
- 繪制視圖
//視圖繪制
- (void)drawInMTKView:(MTKView *)view {
//1.每次渲染都要單獨(dú)創(chuàng)建一個(gè)CommandBuffer
id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];
//獲取渲染描述信息
MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
//2. 從CCAssetReader中讀取圖像數(shù)據(jù)
CMSampleBufferRef sampleBuffer = [self.reader readBuffer];
//3.判斷renderPassDescriptor 和 sampleBuffer 是否已經(jīng)獲取到了?
if(renderPassDescriptor && sampleBuffer)
{
//4.設(shè)置renderPassDescriptor中顏色附著(默認(rèn)背景色)
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.5, 0.5, 1.0f);
//5.根據(jù)渲染描述信息創(chuàng)建渲染命令編碼器
id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
//6.設(shè)置視口大小(顯示區(qū)域)
[renderEncoder setViewport:(MTLViewport){0.0, 0.0, self.viewportSize.x, self.viewportSize.y, -1.0, 1.0 }];
//7.為渲染編碼器設(shè)置渲染管道
[renderEncoder setRenderPipelineState:self.pipelineState];
//8.設(shè)置頂點(diǎn)緩存區(qū)
[renderEncoder setVertexBuffer:self.vertices
offset:0
atIndex:LeoVertexInputIndexVertices];
//9.設(shè)置紋理(將sampleBuffer數(shù)據(jù) 設(shè)置到renderEncoder 中)
[self setupTextureWithEncoder:renderEncoder buffer:sampleBuffer];
//10.設(shè)置片元函數(shù)轉(zhuǎn)化矩陣
[renderEncoder setFragmentBuffer:self.convertMatrix
offset:0
atIndex:LeoFragmentInputIndexMatrix];
//11.開始繪制
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:self.numVertices];
//12.結(jié)束編碼
[renderEncoder endEncoding];
//13.顯示
[commandBuffer presentDrawable:view.currentDrawable];
}
//14.提交命令
[commandBuffer commit];
}
- 將
sampleBuffer數(shù)據(jù) 設(shè)置到renderEncoder中
// 設(shè)置紋理
- (void)setupTextureWithEncoder:(id<MTLRenderCommandEncoder>)encoder buffer:(CMSampleBufferRef)sampleBuffer {
//1.從CMSampleBuffer讀取CVPixelBuffer,
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
id<MTLTexture> textureY = nil;
id<MTLTexture> textureUV = nil;
//textureY 設(shè)置
{
//2.獲取紋理的寬高
size_t width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
size_t height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
//3.像素格式:普通格式,包含一個(gè)8位規(guī)范化的無符號(hào)整數(shù)組件。
MTLPixelFormat pixelFormat = MTLPixelFormatR8Unorm;
//4.創(chuàng)建CoreVideo的Metal紋理
CVMetalTextureRef texture = NULL;
/*5. 根據(jù)視頻像素緩存區(qū) 創(chuàng)建 Metal 紋理緩存區(qū)
CVReturn CVMetalTextureCacheCreateTextureFromImage(CFAllocatorRef allocator,
CVMetalTextureCacheRef textureCache,
CVImageBufferRef sourceImage,
CFDictionaryRef textureAttributes,
MTLPixelFormat pixelFormat,
size_t width,
size_t height,
size_t planeIndex,
CVMetalTextureRef *textureOut);
功能: 從現(xiàn)有圖像緩沖區(qū)創(chuàng)建核心視頻Metal紋理緩沖區(qū)。
參數(shù)1: allocator 內(nèi)存分配器,默認(rèn)kCFAllocatorDefault
參數(shù)2: textureCache 紋理緩存區(qū)對(duì)象
參數(shù)3: sourceImage 視頻圖像緩沖區(qū)
參數(shù)4: textureAttributes 紋理參數(shù)字典.默認(rèn)為NULL
參數(shù)5: pixelFormat 圖像緩存區(qū)數(shù)據(jù)的Metal 像素格式常量.注意如果MTLPixelFormatBGRA8Unorm和攝像頭采集時(shí)設(shè)置的顏色格式不一致,則會(huì)出現(xiàn)圖像異常的情況;
參數(shù)6: width,紋理圖像的寬度(像素)
參數(shù)7: height,紋理圖像的高度(像素)
參數(shù)8: planeIndex.如果圖像緩沖區(qū)是平面的,則為映射紋理數(shù)據(jù)的平面索引。對(duì)于非平面圖像緩沖區(qū)忽略。
參數(shù)9: textureOut,返回時(shí),返回創(chuàng)建的Metal紋理緩沖區(qū)。
*/
CVReturn status = CVMetalTextureCacheCreateTextureFromImage(NULL, self.textureCache, pixelBuffer, NULL, pixelFormat, width, height, 0, &texture);
//6.判斷textureCache 是否創(chuàng)建成功
if(status == kCVReturnSuccess)
{
//7.轉(zhuǎn)成Metal用的紋理
textureY = CVMetalTextureGetTexture(texture);
//8.使用完畢釋放
CFRelease(texture);
}
}
//9.textureUV 設(shè)置(同理,參考于textureY 設(shè)置)
{
size_t width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
size_t height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
MTLPixelFormat pixelFormat = MTLPixelFormatRG8Unorm;
CVMetalTextureRef texture = NULL;
CVReturn status = CVMetalTextureCacheCreateTextureFromImage(NULL, self.textureCache, pixelBuffer, NULL, pixelFormat, width, height, 1, &texture);
if(status == kCVReturnSuccess)
{
textureUV = CVMetalTextureGetTexture(texture);
CFRelease(texture);
}
}
//10.判斷textureY 和 textureUV 是否讀取成功
if(textureY != nil && textureUV != nil)
{
//11.向片元函數(shù)設(shè)置textureY 紋理
[encoder setFragmentTexture:textureY atIndex:LeoFragmentTextureIndexTextureY];
//12.向片元函數(shù)設(shè)置textureUV 紋理
[encoder setFragmentTexture:textureUV atIndex:LeoFragmentTextureIndexTextureUV];
}
//13.使用完畢,則將sampleBuffer 及時(shí)釋放
CFRelease(sampleBuffer);
}