iOS 基于Metal的视频流(AVFoundation采集)渲染流程及详细解析

1.Metal是苹果的图形图像渲染框架,也可以实现普通的GPU高并发计算,流程与OpenGL ES非常类似,且其里面也有封装好的滤镜,可以直接使用无需自己写.metal渲染的代码实现,视情况而定

2.主要思路

Metal视频渲染和图片渲染流程和思路完全一致,只不过图片读取的是图片纹理,视频读取的是Y和UV两个纹理,外加需要传递YUV到RGB的颜色转换矩阵和偏移量

1.OC代码段编写Metal渲染所需的相关流程代码

2.OC和metal文件之间的桥接文件提供数据类型和端口索引

3.编写metal的 vertex顶点函数 fragment片元函数

4.在MTKView的代理方法里面渲染每一帧的图片纹理数据

5.视频纹理的获取和颜色转换矩阵设置

6.视频流捕获AVFoundation,具体在前面的文字里有展开分析

3.具体步骤

1. OC代码段编写Metal渲染所需的相关流程代码

1.创建MTKView,设置代理,这是Metal渲染的目标view,这里的代码都是面向协议的编码,MTKView的 id device,device非常重要,device可以理解成GPU,其他的很多代码都是跟device相关,里面的渲染管道,命令队列,命令缓存区,渲染命令编码器,纹理,各种缓存区,MTLLibrary都需要device生成

2.创建纹理

3.创建顶点

4.创建渲染管道,加载顶点函数和片元函数

2.OC和metal文件之间的桥接文件提供数据类型和端口索引

1.具体的数据结构和索引视情况而定,顶点坐标,纹理坐标,图片纹理,顶点索引,纹理索引,其他需要的参数,比如时间,数组,矩阵,需要的都可以

2.视频的纹理是Y纹理 和 UV纹理两个部分,需要增加YUV到RGB的颜色转换矩阵

3.编写metal的 vertex顶点函数 fragment片元函数

1.编写metal函数,语法看起来有些复杂,其实还好,要注意与桥接对象的数据类型的对应关系,主要有数据结构和端口索引值,类型关键字比如 [[position]] [[buffer]] [[stage_in]] 等等,函数修饰符,参数地址空间修饰符等,其余编码的思路与OpenGL ES基本一致,只是语法有些差异

3.编写metal的 vertex顶点函数 fragment片元函数

1.编写metal函数,语法看起来有些复杂,其实还好,要注意与桥接对象的数据类型的对应关系,主要有数据结构和端口索引值,类型关键字比如 [[position]] [[buffer]] [[stage_in]] 等等,函数修饰符,参数地址空间修饰符等,其余编码的思路与OpenGL ES基本一致,只是语法有些差异

4.在MTKView的代理方法里面渲染每一帧的图片纹理数据
MTKView的两个代理方法
// 设置渲染范围
- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size {
    self.viewportSize = (vector_int2){size.width, size.height};
}

// MTKViewDelegate
// 每一帧渲染命令的具体实现
- (void)drawInMTKView:(nonnull MTKView *)view
5.视频纹理的获取和颜色转换矩阵设置

1.视频流数据CMSampleBuffer或者是CVPixelBuffer 解析成Metal的纹理CVMetalTextureRef 类型,分为Y纹理和UV纹理,需要注意的是采集的颜色输出格式和Y纹理和UV纹理的MTLPixelFormat格式,否则颜色会异常,偏红偏绿偏蓝,这部分获取的Y和UV纹理用来设置MTKView渲染代理的纹理数据

2.设置视频颜色格式YUV和渲染纹理的RGB颜色转换矩阵,这也需要传递的参数

4.代码实现

1.OC类->客户端代码,一般针对于Metal函数 或者OpenGL ES的shader文件,我们写的CPU执行的代码相对于以上两种是GPU执行的代码统称为客户端

1.头文件 变量 属性

#import 
#import 
#import 
#import "VideoCaptureView.h"
#import "YYVideoShaderTypes.h"


@interface MetalVideoFilterView ()

{
    CGRect m_frame;
  
    BOOL isChangeFillMode;
}


// 渲染范围
@property (nonatomic, assign) vector_int2 viewportSize;

// MTKView Metal渲染的view
@property (nonatomic, strong) MTKView * mtkView;

// 用来渲染的设备(GPU)
@property (nonatomic, strong) id  device;

// 渲染管道,管理顶点函数和片元函数
@property (nonatomic, strong) id  renderPipelineState;

// 渲染指令队列
@property (nonatomic, strong) id  commondQueue;

// 顶点缓存对象
@property (nonatomic, strong) id  vertexBuffer;

// 颜色转换矩阵
@property (nonatomic, strong) id  convertMatrix;


// 纹理对象
@property (nonatomic, strong) id  texture;

// 顶点数量
@property (nonatomic, assign) NSUInteger vertexCount;

// 获取视频数据
@property (nonatomic, strong) VideoCaptureView * m_videoCaptureView;

// 视频数据
@property (atomic, assign) CVPixelBufferRef pixelBuffer;

// 纹理缓存 用于使用CVPixelBuffer格式的数据生成Metal的纹理
@property (nonatomic, assign) CVMetalTextureCacheRef textureCache;

// Y纹理
@property (atomic, strong) id  textureY;

// UV纹理
@property (atomic, strong) id  textureUV;

2.初始化调用


- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        m_frame = frame;
        
        
        [self createVideoCaptureView];

        // 1.创建 MTKView
        [self createMTKView];

        // 2.设置顶点
        [self setupVertexsWithWidthScaling:1.0f heightScaling:1.0f];
        
        // 3. 设置颜色转换矩阵 YUV->RGB
        [self setupMatrix];

        // 4.创建渲染管道
        [self createPipeLineState];
    }
    return self;
}

3.创建MTKView


// 创建 MTKView
- (void)createMTKView {
    MTKView * mtkView = [[MTKView alloc] initWithFrame:CGRectMake(0, 0, m_frame.size.width, m_frame.size.height)];
    mtkView.delegate = self;
    
    // 创建Device
    mtkView.device = MTLCreateSystemDefaultDevice();
    
    // 设置device
    self.device = mtkView.device;
    self.viewportSize = (vector_int2){mtkView.drawableSize.width, mtkView.drawableSize.height};
    self.mtkView = mtkView;
    [self addSubview:mtkView];
    
    //3._textureCache的创建(通过CoreVideo提供给CPU/GPU高速缓存通道读取纹理数据)
    CVMetalTextureCacheCreate(NULL, NULL, self.mtkView.device, NULL, &_textureCache);
}

4.设置顶点数据 顶点缓存


- (void)setupVertexsWithWidthScaling:(CGFloat)widthScaling heightScaling:(CGFloat)heightScaling {
    // 1.顶点纹理数组
    // 顶点x,y,z,w  纹理x,y
    // 因为图片和视频的默认纹理是反的 左上 00 右上10 左下 01 右下11
    // // 左下 右下
    YYVideoVertex vertexArray[] = {
        {{-1.0 * widthScaling, -1.0 * heightScaling, 0.0, 1.0}, {0.0, 1.0}},
        {{1.0 * widthScaling, -1.0 * heightScaling, 0.0, 1.0}, {1.0, 1.0}},
        {{-1.0 * widthScaling, 1.0 * heightScaling, 0.0, 1.0}, {0.0, 0.0}}, //左上
        {{1.0 * widthScaling, 1.0 * heightScaling, 0.0, 1.0}, {1.0, 0.0}}, // 右上
    };
    
    // 2.生成顶点缓存
    // MTLResourceStorageModeShared 属性可共享的,表示可以被顶点或者片元函数或者其他函数使用
    self.vertexBuffer = [self.device newBufferWithBytes:vertexArray length:sizeof(vertexArray) options:MTLResourceStorageModeShared];
    
    // 3.获取顶点数量
    self.vertexCount = sizeof(vertexArray) / sizeof(YYVideoVertex);
}

5.设置渲染管道

// 4.创建渲染管道
// 根据.metal里的函数名,使用MTLLibrary创建顶点函数和片元函数
// 从这里可以看出来,MTLLibrary里面包含所有.metal的文件,所以,不同的.metal里面的函数名不能相同
// id  创建library、MTLRenderPipelineState、MTLCommandQueue
- (void)createPipeLineState {
    
    // 1.从项目中加载.metal文件,创建一个library
    id  library = [self.device newDefaultLibrary];
    // id  -> id 
    
    // 2.从库中MTLLibrary,加载顶点函数
    id  vertexFunction = [library newFunctionWithName:@"vertexVideoShader"];
    
    // 3.从库中MTLLibrary,加载顶点函数
    id  fragmentFunction = [library newFunctionWithName:@"fragmentVideoShader"];
    
    // 4.创建管道渲染管道描述符
    MTLRenderPipelineDescriptor * renderPipeDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
    
    // 5.设置管道顶点函数和片元函数
    renderPipeDescriptor.vertexFunction = vertexFunction;
    renderPipeDescriptor.fragmentFunction = fragmentFunction;
    
    // 6.设置管道描述的关联颜色存储方式
    renderPipeDescriptor.colorAttachments[0].pixelFormat = self.mtkView.colorPixelFormat;
    
    NSError * error = nil;
    // 7.根据渲染管道描述符 创建渲染管道
    id  renderPipelineState = [self.device newRenderPipelineStateWithDescriptor:renderPipeDescriptor error:&error];
    self.renderPipelineState = renderPipelineState;
    // id  -> id 
    
    // 8. 创建渲染指令队列
    id  commondQueue = [self.device newCommandQueue];
    self.commondQueue = commondQueue;
    // id  -> id 
}

6.设置颜色转换矩阵和偏移量的 数据结构

// 设置YUV->RGB转换的矩阵
- (void)setupMatrix {
    
    //1.转化矩阵
    // 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 = (matrix_float3x3){
        (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.创建转化矩阵结构体.
    YYVideoYUVToRGBConvertMatrix matrix;
    //设置转化矩阵
    /*
     kColorConversion601DefaultMatrix;
     kColorConversion601FullRangeMatrix;
     kColorConversion709DefaultMatrix;
     */
    matrix.matrix = kColorConversion601FullRangeMatrix;
    //设置offset偏移量
    matrix.offset = kColorConversion601FullRangeOffset;
    
    //4.创建转换矩阵缓存区.
    self.convertMatrix = [self.mtkView.device newBufferWithBytes:&matrix length:sizeof(YYVideoYUVToRGBConvertMatrix) options:MTLResourceStorageModeShared];
}

7.或者视频流转换为Y和UV纹理

// 视频捕捉的View
- (void)createVideoCaptureView {
    VideoCaptureView * videoCaptureView = [[VideoCaptureView alloc] initWithFrame:CGRectMake(0, 0, m_frame.size.width, m_frame.size.height)];
    videoCaptureView.m_delegate = self;
    self.m_videoCaptureView = videoCaptureView;
}

// 捕获视频的代理回调方法
- (void)outputCVPixelBuffer:(CVPixelBufferRef)pixelBuffer {
    // 视频对外输出一般要CMSampleBufferRef从中提取出CVPixelBufferRef
    // CVPixelBufferRetain 使用这个强引用
    if (_pixelBuffer) {
        CFRelease(_pixelBuffer);
    }
    _pixelBuffer = CVPixelBufferRetain(pixelBuffer);
    
    [self setupTextureWithPixelBuffer:_pixelBuffer];

//    NSLog(@"outputSampleBuffer");
    // AVFoundation采集的CMSampleBufferRef不需要释放,因为只是参数,代理方法内部有释放
    // CFRelease(sampleBuffer);
}

// 生成Y纹理和UV纹理,提供给MTKView的代理方法使用
- (void)setupTextureWithPixelBuffer:(CVPixelBufferRef)pixelBuffer {
    if (!pixelBuffer) {
        return;
    }
   
   
    // Y纹理
    {
        // 0表示Y纹理
        size_t width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
        size_t height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
        

        if (isChangeFillMode) {
            isChangeFillMode = NO;
            [self resetVertexWithWidth:width height:height];
        }
        
        // 像素颜色格式 MTLPixelFormatR8Unorm 表示只取R一个颜色分支
        MTLPixelFormat pixelFormat = MTLPixelFormatR8Unorm;

        CVMetalTextureRef texture = NULL;
        CVMetalTextureCacheRef textureCache = NULL;
        
        // 开辟纹理缓存区
        CVReturn TextureCacheCreateStatus =CVMetalTextureCacheCreate(NULL, NULL, self.device, NULL, &textureCache);
        if(TextureCacheCreateStatus == kCVReturnSuccess) {
//            NSLog(@"CVMetalTextureCacheCreate is success");
        }
        
        // 根据CVPixelBufferRef数据,使用CVMetalTextureCacheRef,创建CVMetalTextureRef
        // 0表示Y纹理
        CVReturn status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, self.textureCache, pixelBuffer, NULL, pixelFormat, width, height, 0, &texture);
        if(status == kCVReturnSuccess) {
            // 根据纹理CVMetalTextureRef 创建id 
            self.textureY = CVMetalTextureGetTexture(texture);
            
            // 使用完毕释放资源
            CFRelease(texture);
            
//            NSLog(@"create Y texture is Success");
        } else {
//            NSLog(@"create Y texture is failed");
//            NSLog(@"status == %d", status);
        }
        CFRelease(textureCache);
    }
    
    // UV纹理
    {
        // 1表示UV纹理
        size_t width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
        size_t height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
        
        // 像素颜色格式 MTLPixelFormatRG8Unorm 表示只取RG两个颜色分支
        MTLPixelFormat pixelFormat = MTLPixelFormatRG8Unorm;
        
        CVMetalTextureRef texture = NULL;
        CVMetalTextureCacheRef textureCache = NULL;
        // 开辟纹理缓存区
        CVReturn TextureCacheCreateStatus =CVMetalTextureCacheCreate(NULL, NULL, self.device, NULL, &textureCache);
        if(TextureCacheCreateStatus == kCVReturnSuccess) {
//            NSLog(@"CVMetalTextureCacheCreate is success");
        }
        
        // 根据CVPixelBufferRef数据,使用CVMetalTextureCacheRef,创建CVMetalTextureRef
        // 1表示UV纹理
        
        CVReturn status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, self.textureCache, pixelBuffer, NULL, pixelFormat, width, height, 1, &texture);
        if(status == kCVReturnSuccess) {
            // 根据纹理CVMetalTextureRef 创建id 
            self.textureUV = CVMetalTextureGetTexture(texture);
            
            // 使用完毕释放资源
            CFRelease(texture);
            
//            NSLog(@"create UV texture is Success");
        } else {
//            NSLog(@"create UV texture is failed");
//            NSLog(@"status == %d", status);
        }
        
        CFRelease(textureCache);
    }
}

8.MTKView代理方法

// MTKViewDelegate 设置渲染区域或者是区域变动时执行
- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size {
    self.viewportSize = (vector_int2){size.width, size.height};
}

// MTKViewDelegate 每一帧的渲染流程
- (void)drawInMTKView:(nonnull MTKView *)view {
    if (self.textureY && self.textureUV) {
        // 1.为当前渲染的每个渲染传递创建一个新的命令缓冲区
        id  commandBuffer = [self.commondQueue commandBuffer];
        
        //指定缓存区名称
        commandBuffer.label = @"EachCommand";
        
        // 2.获取渲染命令编码器 MTLRenderCommandEncoder的描述符
        // currentRenderPassDescriptor描述符包含currentDrawable's的纹理、视图的深度、模板和sample缓冲区和清晰的值。
        // MTLRenderPassDescriptor描述一系列attachments的值,类似GL的FrameBuffer;同时也用来创建MTLRenderCommandEncoder
        MTLRenderPassDescriptor * renderPassDescriptor = view.currentRenderPassDescriptor;
        if (renderPassDescriptor) {
            // 设置默认颜色 背景色
            renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(1.0, 1.0, 1.0, 1.0f);
            
            // 3.根据描述创建x 渲染命令编码器
            id  renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
            
            //        typedef struct {
            //            double originX, originY, width, height, znear, zfar;
            //        } MTLViewport;
            // 4.设置绘制区域
            [renderEncoder setViewport:(MTLViewport){0, 0, self.viewportSize.x, self.viewportSize.y, -1.0, 1.0}];
            
            // 5.设置渲染管道
            [renderEncoder setRenderPipelineState:self.renderPipelineState];
            
            // 6.传递顶点缓存
            [renderEncoder setVertexBuffer:self.vertexBuffer offset:0 atIndex:YYVideoVertexInputIndexVertexs];
            
            // 7.传递颜色转换矩阵
            [renderEncoder setFragmentBuffer:self.convertMatrix offset:0 atIndex:YYVideoConvertMatrixIndexYUVToRGB];
            
            // 设置Y UV纹理
            if (self.textureY) {
//                NSLog(@"setFragmentTexture textureY");
                [renderEncoder setFragmentTexture:self.textureY atIndex:YYVidoTextureIndexYTexture];
                self.textureY = nil;
            }
            
            if (self.textureUV) {
//                NSLog(@"setFragmentTexture textureUV");
                [renderEncoder setFragmentTexture:self.textureUV atIndex:YYVidoTextureIndexUVTexture];
                self.textureUV = nil;
            }
            
            // 8.绘制
            [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:self.vertexCount];
            
            // 9.命令结束
            [renderEncoder endEncoding];
            
            // 10.显示
            [commandBuffer presentDrawable:view.currentDrawable];
        }
        // 11. 提交
        [commandBuffer commit];
    }
    
}

9.视频显示模式设置,同Image显示模式设置

- (void)resetVertexWithWidth:(CGFloat)width height:(CGFloat)height  {
 
    dispatch_async(dispatch_get_main_queue(), ^{
        CGSize inputImageSize = CGSizeMake(width, height);
        CGFloat heightScaling = 1.0, widthScaling = 1.0;
        CGSize currentViewSize = self.bounds.size;

        CGRect insetRect = AVMakeRectWithAspectRatioInsideRect(inputImageSize, self.bounds);
        switch(self.fillMode)
        {
            case kMetalVideoFilterViewFillModeStretch:
                
            {
                widthScaling = 1.0;
                heightScaling = 1.0;
            };
                break;
                
            case kMetalVideoFilterViewFillModePreserveAspectRatio:
            {
                widthScaling = insetRect.size.width / currentViewSize.width;
                heightScaling = insetRect.size.height / currentViewSize.height;
            };
            break;
                
            case kMetalVideoFilterViewFillModePreserveAspectRatioAndFill:
            {
                widthScaling = currentViewSize.height / insetRect.size.height;
                heightScaling = currentViewSize.width / insetRect.size.width;
            };
                break;
        }
        
//        NSLog(@"widthScaling == %lf", widthScaling);
//        NSLog(@"heightScaling == %lf", heightScaling);
        [self setupVertexsWithWidthScaling:widthScaling heightScaling:heightScaling];
    });
}

- (void)setFillMode:(kMetalVideoFilterViewFillModeType)fillMode {
    isChangeFillMode = YES;
    _fillMode = fillMode;
}
2.桥接类 .h文件,里面有数据结构和端口索引值

simd这个类提供一些公共的数据类型,客户端和Metal都可以使用的类型

#include 

// 顶点坐标和纹理坐标数据结构
typedef struct {
    // 顶点坐标 4维向量
    vector_float4 position;
    
    // 纹理坐标
    vector_float2 textureCoordinate;
    
} YYVideoVertex;

//颜色转换数据结构 YUV转RGBA 转换矩阵
typedef struct {
    
    //三维矩阵
    matrix_float3x3 matrix;
    //偏移量
    vector_float3 offset;
    
} YYVideoYUVToRGBConvertMatrix;


// 顶点index
typedef enum {
    
    YYVideoVertexInputIndexVertexs = 0,
    
} YYVideoVertexInputIndex;


// 纹理 index
typedef enum {
    
    // Y纹理索引 index
    YYVidoTextureIndexYTexture = 0,
    
    // UV纹理索引 index
    YYVidoTextureIndexUVTexture = 1,
    
} YYVideoTextureIndex;


// 颜色转换结构体 index
typedef enum {
    
    YYVideoConvertMatrixIndexYUVToRGB = 0,
    
    
} YYVideoConvertMatrixIndex;

#endif /* YYVideoShaderTypes_h */
3.Metal代码实现 顶点片元函数实现

1.需要注意,函数修饰符,变量修饰符,地址空间修饰符,桥接或者输出的数据结构类型,索引值,内部的逻辑编码与OpenGL ES非常类似,逻辑一致


#include 
#import "YYVideoShaderTypes.h"

using namespace metal;

// 定义了一个类型为RasterizerData的结构体,里面有一个float4向量和float2向量,其中float4被[[position]]修饰,其表示的变量为顶点

typedef struct {
    // float4 4维向量 clipSpacePosition参数名
    // position 修饰符的表示顶点 语法是[[position]],这是苹果内置的语法和position关键字不能改变
    float4 clipSpacePosition [[position]];
    
    // float2 2维向量  表示纹理
    float2 textureCoordinate;
    
} RasterizerData;

// 顶点函数通过一个自定义的结构体,返回对应的数据,顶点函数的输入参数也可以是自定义结构体

// 顶点函数
// vertex 函数修饰符表示顶点函数,
// RasterizerData返回值类型,
// vertexImageShader函数名
// vertex_id 顶点id修饰符,苹果内置不可变,[[vertex_id]]
// buffer 缓存数据修饰符,苹果内置不可变,YYImageVertexInputIndexVertexs是索引
// [[buffer(YYImageVertexInputIndexVertexs)]]
// constant 变量类型修饰符,表示存储在device区域

vertex RasterizerData vertexVideoShader(uint vertexID [[vertex_id]], constant YYVideoVertex * vertexArray [[buffer(YYVideoVertexInputIndexVertexs)]]) {
    
    RasterizerData outData;
    
    // 获取YYVertex里面的顶点坐标和纹理坐标
    outData.clipSpacePosition = vertexArray[vertexID].position;
    outData.textureCoordinate = vertexArray[vertexID].textureCoordinate;
    
    return outData;
}

// 片元函数
// fragment 函数修饰符表示片元函数 float4 返回值类型->颜色RGBA fragmentImageShader 函数名
// RasterizerData 参数类型 input 变量名
// [[stage_in] stage_in表示这个数据来自光栅化。(光栅化是顶点处理之后的步骤,业务层无法修改)
// texture2d 类型表示纹理 baseTexture 变量名
// [[ texture(index)]] 纹理修饰符
// 可以加索引 [[ texture(0)]]纹理0, [[ texture(1)]]纹理1
// YYImageTextureIndexBaseTexture表示纹理索引

fragment float4 fragmentVideoShader(RasterizerData input [[stage_in]], texture2d textureY [[texture (YYVidoTextureIndexYTexture)]], texture2d textureUV [[texture(YYVidoTextureIndexUVTexture)]], constant YYVideoYUVToRGBConvertMatrix * convertMatrix [[buffer(YYVideoConvertMatrixIndexYUVToRGB)]]) {
    
    // constexpr 修饰符
    // sampler 采样器
    // textureSampler 采样器变量名
    // mag_filter:: linear, min_filter:: linear 设置放大缩小过滤方式
    constexpr sampler textureSampler(mag_filter:: linear, min_filter:: linear);
    
    // 得到纹理对应位置的颜色
//    float YColor = textureY.sample(textureSampler, input.textureCoordinate).r;
//    float2 UVColor = textureUV.sample(textureSampler, input.textureCoordinate).rg;
//    float3 color = float3(YColor, UVColor);
//    float3 outputColor = matrix->matrix * (color + matrix->offset);
    
    float3 yuv = float3(textureY.sample(textureSampler, input.textureCoordinate).r,
                        textureUV.sample(textureSampler, input.textureCoordinate).rg);
    
    //3.将YUV 转化为 RGB值.convertMatrix->matrix * (YUV + convertMatrix->offset)
    float3 rgb = convertMatrix->matrix * (yuv + convertMatrix->offset);

    // 返回颜色值
    return float4(rgb, 1.0);
}

你可能感兴趣的:(iOS 基于Metal的视频流(AVFoundation采集)渲染流程及详细解析)