iOS 基于Metal的視頻流(AVFoundation采集)渲染流程及詳細解析

1.Metal是蘋果的圖形圖像渲染框架,也可以實現(xiàn)普通的GPU高并發(fā)計算,流程與OpenGL ES非常類似,且其里面也有封裝好的濾鏡,可以直接使用無需自己寫.metal渲染的代碼實現(xiàn),視情況而定

2.主要思路

Metal視頻渲染和圖片渲染流程和思路完全一致,只不過圖片讀取的是圖片紋理,視頻讀取的是Y和UV兩個紋理,外加需要傳遞YUV到RGB的顏色轉(zhuǎn)換矩陣和偏移量

1.OC代碼段編寫Metal渲染所需的相關(guān)流程代碼

2.OC和metal文件之間的橋接文件提供數(shù)據(jù)類型和端口索引

3.編寫metal的 vertex頂點函數(shù) fragment片元函數(shù)

4.在MTKView的代理方法里面渲染每一幀的圖片紋理數(shù)據(jù)

5.視頻紋理的獲取和顏色轉(zhuǎn)換矩陣設(shè)置

6.視頻流捕獲AVFoundation,具體在前面的文字里有展開分析

3.具體步驟

1. OC代碼段編寫Metal渲染所需的相關(guān)流程代碼

1.創(chuàng)建MTKView,設(shè)置代理,這是Metal渲染的目標(biāo)view,這里的代碼都是面向協(xié)議的編碼,MTKView的 id <MTLDevice> device,device非常重要,device可以理解成GPU,其他的很多代碼都是跟device相關(guān),里面的渲染管道,命令隊列,命令緩存區(qū),渲染命令編碼器,紋理,各種緩存區(qū),MTLLibrary都需要device生成

2.創(chuàng)建紋理

3.創(chuàng)建頂點

4.創(chuàng)建渲染管道,加載頂點函數(shù)和片元函數(shù)

2.OC和metal文件之間的橋接文件提供數(shù)據(jù)類型和端口索引

1.具體的數(shù)據(jù)結(jié)構(gòu)和索引視情況而定,頂點坐標(biāo),紋理坐標(biāo),圖片紋理,頂點索引,紋理索引,其他需要的參數(shù),比如時間,數(shù)組,矩陣,需要的都可以

2.視頻的紋理是Y紋理 和 UV紋理兩個部分,需要增加YUV到RGB的顏色轉(zhuǎn)換矩陣

3.編寫metal的 vertex頂點函數(shù) fragment片元函數(shù)

1.編寫metal函數(shù),語法看起來有些復(fù)雜,其實還好,要注意與橋接對象的數(shù)據(jù)類型的對應(yīng)關(guān)系,主要有數(shù)據(jù)結(jié)構(gòu)和端口索引值,類型關(guān)鍵字比如 [[position]] [[buffer]] [[stage_in]] 等等,函數(shù)修飾符,參數(shù)地址空間修飾符等,其余編碼的思路與OpenGL ES基本一致,只是語法有些差異

3.編寫metal的 vertex頂點函數(shù) fragment片元函數(shù)

1.編寫metal函數(shù),語法看起來有些復(fù)雜,其實還好,要注意與橋接對象的數(shù)據(jù)類型的對應(yīng)關(guān)系,主要有數(shù)據(jù)結(jié)構(gòu)和端口索引值,類型關(guān)鍵字比如 [[position]] [[buffer]] [[stage_in]] 等等,函數(shù)修飾符,參數(shù)地址空間修飾符等,其余編碼的思路與OpenGL ES基本一致,只是語法有些差異

4.在MTKView的代理方法里面渲染每一幀的圖片紋理數(shù)據(jù)
MTKView的兩個代理方法
// 設(shè)置渲染范圍
- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size {
    self.viewportSize = (vector_int2){size.width, size.height};
}

// MTKViewDelegate
// 每一幀渲染命令的具體實現(xiàn)
- (void)drawInMTKView:(nonnull MTKView *)view
5.視頻紋理的獲取和顏色轉(zhuǎn)換矩陣設(shè)置

1.視頻流數(shù)據(jù)CMSampleBuffer或者是CVPixelBuffer 解析成Metal的紋理CVMetalTextureRef 類型,分為Y紋理和UV紋理,需要注意的是采集的顏色輸出格式和Y紋理和UV紋理的MTLPixelFormat格式,否則顏色會異常,偏紅偏綠偏藍,這部分獲取的Y和UV紋理用來設(shè)置MTKView渲染代理的紋理數(shù)據(jù)

2.設(shè)置視頻顏色格式Y(jié)UV和渲染紋理的RGB顏色轉(zhuǎn)換矩陣,這也需要傳遞的參數(shù)

4.代碼實現(xiàn)

1.OC類->客戶端代碼,一般針對于Metal函數(shù) 或者OpenGL ES的shader文件,我們寫的CPU執(zhí)行的代碼相對于以上兩種是GPU執(zhí)行的代碼統(tǒng)稱為客戶端

1.頭文件 變量 屬性

#import <MetalKit/MetalKit.h>
#import <Metal/Metal.h>
#import <AVFoundation/AVFoundation.h>
#import "VideoCaptureView.h"
#import "YYVideoShaderTypes.h"


@interface MetalVideoFilterView ()
<MTKViewDelegate, VideoCaptureViewDelegate>
{
    CGRect m_frame;
  
    BOOL isChangeFillMode;
}


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

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

// 用來渲染的設(shè)備(GPU)
@property (nonatomic, strong) id <MTLDevice> device;

// 渲染管道,管理頂點函數(shù)和片元函數(shù)
@property (nonatomic, strong) id <MTLRenderPipelineState> renderPipelineState;

// 渲染指令隊列
@property (nonatomic, strong) id <MTLCommandQueue> commondQueue;

// 頂點緩存對象
@property (nonatomic, strong) id <MTLBuffer> vertexBuffer;

// 顏色轉(zhuǎn)換矩陣
@property (nonatomic, strong) id <MTLBuffer> convertMatrix;


// 紋理對象
@property (nonatomic, strong) id <MTLTexture> texture;

// 頂點數(shù)量
@property (nonatomic, assign) NSUInteger vertexCount;

// 獲取視頻數(shù)據(jù)
@property (nonatomic, strong) VideoCaptureView * m_videoCaptureView;

// 視頻數(shù)據(jù)
@property (atomic, assign) CVPixelBufferRef pixelBuffer;

// 紋理緩存 用于使用CVPixelBuffer格式的數(shù)據(jù)生成Metal的紋理
@property (nonatomic, assign) CVMetalTextureCacheRef textureCache;

// Y紋理
@property (atomic, strong) id <MTLTexture> textureY;

// UV紋理
@property (atomic, strong) id <MTLTexture> textureUV;

2.初始化調(diào)用


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

        // 1.創(chuàng)建 MTKView
        [self createMTKView];

        // 2.設(shè)置頂點
        [self setupVertexsWithWidthScaling:1.0f heightScaling:1.0f];
        
        // 3. 設(shè)置顏色轉(zhuǎn)換矩陣 YUV->RGB
        [self setupMatrix];

        // 4.創(chuàng)建渲染管道
        [self createPipeLineState];
    }
    return self;
}

3.創(chuàng)建MTKView


// 創(chuàng)建 MTKView
- (void)createMTKView {
    MTKView * mtkView = [[MTKView alloc] initWithFrame:CGRectMake(0, 0, m_frame.size.width, m_frame.size.height)];
    mtkView.delegate = self;
    
    // 創(chuàng)建Device
    mtkView.device = MTLCreateSystemDefaultDevice();
    
    // 設(shè)置device
    self.device = mtkView.device;
    self.viewportSize = (vector_int2){mtkView.drawableSize.width, mtkView.drawableSize.height};
    self.mtkView = mtkView;
    [self addSubview:mtkView];
    
    //3._textureCache的創(chuàng)建(通過CoreVideo提供給CPU/GPU高速緩存通道讀取紋理數(shù)據(jù))
    CVMetalTextureCacheCreate(NULL, NULL, self.mtkView.device, NULL, &_textureCache);
}

4.設(shè)置頂點數(shù)據(jù) 頂點緩存


- (void)setupVertexsWithWidthScaling:(CGFloat)widthScaling heightScaling:(CGFloat)heightScaling {
    // 1.頂點紋理數(shù)組
    // 頂點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 屬性可共享的,表示可以被頂點或者片元函數(shù)或者其他函數(shù)使用
    self.vertexBuffer = [self.device newBufferWithBytes:vertexArray length:sizeof(vertexArray) options:MTLResourceStorageModeShared];
    
    // 3.獲取頂點數(shù)量
    self.vertexCount = sizeof(vertexArray) / sizeof(YYVideoVertex);
}

5.設(shè)置渲染管道

// 4.創(chuàng)建渲染管道
// 根據(jù).metal里的函數(shù)名,使用MTLLibrary創(chuàng)建頂點函數(shù)和片元函數(shù)
// 從這里可以看出來,MTLLibrary里面包含所有.metal的文件,所以,不同的.metal里面的函數(shù)名不能相同
// id <MTLDevice> 創(chuàng)建library、MTLRenderPipelineState、MTLCommandQueue
- (void)createPipeLineState {
    
    // 1.從項目中加載.metal文件,創(chuàng)建一個library
    id <MTLLibrary> library = [self.device newDefaultLibrary];
    // id <MTLDevice> -> id <MTLLibrary>
    
    // 2.從庫中MTLLibrary,加載頂點函數(shù)
    id <MTLFunction> vertexFunction = [library newFunctionWithName:@"vertexVideoShader"];
    
    // 3.從庫中MTLLibrary,加載頂點函數(shù)
    id <MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragmentVideoShader"];
    
    // 4.創(chuàng)建管道渲染管道描述符
    MTLRenderPipelineDescriptor * renderPipeDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
    
    // 5.設(shè)置管道頂點函數(shù)和片元函數(shù)
    renderPipeDescriptor.vertexFunction = vertexFunction;
    renderPipeDescriptor.fragmentFunction = fragmentFunction;
    
    // 6.設(shè)置管道描述的關(guān)聯(lián)顏色存儲方式
    renderPipeDescriptor.colorAttachments[0].pixelFormat = self.mtkView.colorPixelFormat;
    
    NSError * error = nil;
    // 7.根據(jù)渲染管道描述符 創(chuàng)建渲染管道
    id <MTLRenderPipelineState> renderPipelineState = [self.device newRenderPipelineStateWithDescriptor:renderPipeDescriptor error:&error];
    self.renderPipelineState = renderPipelineState;
    // id <MTLDevice> -> id <MTLRenderPipelineState>
    
    // 8. 創(chuàng)建渲染指令隊列
    id <MTLCommandQueue> commondQueue = [self.device newCommandQueue];
    self.commondQueue = commondQueue;
    // id <MTLDevice> -> id <MTLCommandQueue>
}

6.設(shè)置顏色轉(zhuǎn)換矩陣和偏移量的 數(shù)據(jù)結(jié)構(gòu)

// 設(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 = (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.創(chuàng)建轉(zhuǎn)化矩陣結(jié)構(gòu)體.
    YYVideoYUVToRGBConvertMatrix 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(YYVideoYUVToRGBConvertMatrix) options:MTLResourceStorageModeShared];
}

7.或者視頻流轉(zhuǎn)換為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;
}

// 捕獲視頻的代理回調(diào)方法
- (void)outputCVPixelBuffer:(CVPixelBufferRef)pixelBuffer {
    // 視頻對外輸出一般要CMSampleBufferRef從中提取出CVPixelBufferRef
    // CVPixelBufferRetain 使用這個強引用
    if (_pixelBuffer) {
        CFRelease(_pixelBuffer);
    }
    _pixelBuffer = CVPixelBufferRetain(pixelBuffer);
    
    [self setupTextureWithPixelBuffer:_pixelBuffer];

//    NSLog(@"outputSampleBuffer");
    // AVFoundation采集的CMSampleBufferRef不需要釋放,因為只是參數(shù),代理方法內(nèi)部有釋放
    // 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;
        
        // 開辟紋理緩存區(qū)
        CVReturn TextureCacheCreateStatus =CVMetalTextureCacheCreate(NULL, NULL, self.device, NULL, &textureCache);
        if(TextureCacheCreateStatus == kCVReturnSuccess) {
//            NSLog(@"CVMetalTextureCacheCreate is success");
        }
        
        // 根據(jù)CVPixelBufferRef數(shù)據(jù),使用CVMetalTextureCacheRef,創(chuàng)建CVMetalTextureRef
        // 0表示Y紋理
        CVReturn status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, self.textureCache, pixelBuffer, NULL, pixelFormat, width, height, 0, &texture);
        if(status == kCVReturnSuccess) {
            // 根據(jù)紋理CVMetalTextureRef 創(chuàng)建id <MTLTexture>
            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;
        // 開辟紋理緩存區(qū)
        CVReturn TextureCacheCreateStatus =CVMetalTextureCacheCreate(NULL, NULL, self.device, NULL, &textureCache);
        if(TextureCacheCreateStatus == kCVReturnSuccess) {
//            NSLog(@"CVMetalTextureCacheCreate is success");
        }
        
        // 根據(jù)CVPixelBufferRef數(shù)據(jù),使用CVMetalTextureCacheRef,創(chuàng)建CVMetalTextureRef
        // 1表示UV紋理
        
        CVReturn status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, self.textureCache, pixelBuffer, NULL, pixelFormat, width, height, 1, &texture);
        if(status == kCVReturnSuccess) {
            // 根據(jù)紋理CVMetalTextureRef 創(chuàng)建id <MTLTexture>
            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 設(shè)置渲染區(qū)域或者是區(qū)域變動時執(zhí)行
- (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.為當(dāng)前渲染的每個渲染傳遞創(chuàng)建一個新的命令緩沖區(qū)
        id <MTLCommandBuffer> commandBuffer = [self.commondQueue commandBuffer];
        
        //指定緩存區(qū)名稱
        commandBuffer.label = @"EachCommand";
        
        // 2.獲取渲染命令編碼器 MTLRenderCommandEncoder的描述符
        // currentRenderPassDescriptor描述符包含currentDrawable's的紋理、視圖的深度、模板和sample緩沖區(qū)和清晰的值。
        // MTLRenderPassDescriptor描述一系列attachments的值,類似GL的FrameBuffer;同時也用來創(chuàng)建MTLRenderCommandEncoder
        MTLRenderPassDescriptor * renderPassDescriptor = view.currentRenderPassDescriptor;
        if (renderPassDescriptor) {
            // 設(shè)置默認顏色 背景色
            renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(1.0, 1.0, 1.0, 1.0f);
            
            // 3.根據(jù)描述創(chuàng)建x 渲染命令編碼器
            id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
            
            //        typedef struct {
            //            double originX, originY, width, height, znear, zfar;
            //        } MTLViewport;
            // 4.設(shè)置繪制區(qū)域
            [renderEncoder setViewport:(MTLViewport){0, 0, self.viewportSize.x, self.viewportSize.y, -1.0, 1.0}];
            
            // 5.設(shè)置渲染管道
            [renderEncoder setRenderPipelineState:self.renderPipelineState];
            
            // 6.傳遞頂點緩存
            [renderEncoder setVertexBuffer:self.vertexBuffer offset:0 atIndex:YYVideoVertexInputIndexVertexs];
            
            // 7.傳遞顏色轉(zhuǎn)換矩陣
            [renderEncoder setFragmentBuffer:self.convertMatrix offset:0 atIndex:YYVideoConvertMatrixIndexYUVToRGB];
            
            // 設(shè)置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.命令結(jié)束
            [renderEncoder endEncoding];
            
            // 10.顯示
            [commandBuffer presentDrawable:view.currentDrawable];
        }
        // 11. 提交
        [commandBuffer commit];
    }
    
}

9.視頻顯示模式設(shè)置,同Image顯示模式設(shè)置

- (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文件,里面有數(shù)據(jù)結(jié)構(gòu)和端口索引值

simd這個類提供一些公共的數(shù)據(jù)類型,客戶端和Metal都可以使用的類型

#include <simd/simd.h>

// 頂點坐標(biāo)和紋理坐標(biāo)數(shù)據(jù)結(jié)構(gòu)
typedef struct {
    // 頂點坐標(biāo) 4維向量
    vector_float4 position;
    
    // 紋理坐標(biāo)
    vector_float2 textureCoordinate;
    
} YYVideoVertex;

//顏色轉(zhuǎn)換數(shù)據(jù)結(jié)構(gòu) YUV轉(zhuǎn)RGBA 轉(zhuǎn)換矩陣
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;


// 顏色轉(zhuǎn)換結(jié)構(gòu)體 index
typedef enum {
    
    YYVideoConvertMatrixIndexYUVToRGB = 0,
    
    
} YYVideoConvertMatrixIndex;

#endif /* YYVideoShaderTypes_h */
3.Metal代碼實現(xiàn) 頂點片元函數(shù)實現(xiàn)

1.需要注意,函數(shù)修飾符,變量修飾符,地址空間修飾符,橋接或者輸出的數(shù)據(jù)結(jié)構(gòu)類型,索引值,內(nèi)部的邏輯編碼與OpenGL ES非常類似,邏輯一致


#include <metal_stdlib>
#import "YYVideoShaderTypes.h"

using namespace metal;

// 定義了一個類型為RasterizerData的結(jié)構(gòu)體,里面有一個float4向量和float2向量,其中float4被[[position]]修飾,其表示的變量為頂點

typedef struct {
    // float4 4維向量 clipSpacePosition參數(shù)名
    // position 修飾符的表示頂點 語法是[[position]],這是蘋果內(nèi)置的語法和position關(guān)鍵字不能改變
    float4 clipSpacePosition [[position]];
    
    // float2 2維向量  表示紋理
    float2 textureCoordinate;
    
} RasterizerData;

// 頂點函數(shù)通過一個自定義的結(jié)構(gòu)體,返回對應(yīng)的數(shù)據(jù),頂點函數(shù)的輸入?yún)?shù)也可以是自定義結(jié)構(gòu)體

// 頂點函數(shù)
// vertex 函數(shù)修飾符表示頂點函數(shù),
// RasterizerData返回值類型,
// vertexImageShader函數(shù)名
// vertex_id 頂點id修飾符,蘋果內(nèi)置不可變,[[vertex_id]]
// buffer 緩存數(shù)據(jù)修飾符,蘋果內(nèi)置不可變,YYImageVertexInputIndexVertexs是索引
// [[buffer(YYImageVertexInputIndexVertexs)]]
// constant 變量類型修飾符,表示存儲在device區(qū)域

vertex RasterizerData vertexVideoShader(uint vertexID [[vertex_id]], constant YYVideoVertex * vertexArray [[buffer(YYVideoVertexInputIndexVertexs)]]) {
    
    RasterizerData outData;
    
    // 獲取YYVertex里面的頂點坐標(biāo)和紋理坐標(biāo)
    outData.clipSpacePosition = vertexArray[vertexID].position;
    outData.textureCoordinate = vertexArray[vertexID].textureCoordinate;
    
    return outData;
}

// 片元函數(shù)
// fragment 函數(shù)修飾符表示片元函數(shù) float4 返回值類型->顏色RGBA fragmentImageShader 函數(shù)名
// RasterizerData 參數(shù)類型 input 變量名
// [[stage_in] stage_in表示這個數(shù)據(jù)來自光柵化。(光柵化是頂點處理之后的步驟,業(yè)務(wù)層無法修改)
// texture2d 類型表示紋理 baseTexture 變量名
// [[ texture(index)]] 紋理修飾符
// 可以加索引 [[ texture(0)]]紋理0, [[ texture(1)]]紋理1
// YYImageTextureIndexBaseTexture表示紋理索引

fragment float4 fragmentVideoShader(RasterizerData input [[stage_in]], texture2d<float> textureY [[texture (YYVidoTextureIndexYTexture)]], texture2d<float> textureUV [[texture(YYVidoTextureIndexUVTexture)]], constant YYVideoYUVToRGBConvertMatrix * convertMatrix [[buffer(YYVideoConvertMatrixIndexYUVToRGB)]]) {
    
    // constexpr 修飾符
    // sampler 采樣器
    // textureSampler 采樣器變量名
    // mag_filter:: linear, min_filter:: linear 設(shè)置放大縮小過濾方式
    constexpr sampler textureSampler(mag_filter:: linear, min_filter:: linear);
    
    // 得到紋理對應(yīng)位置的顏色
//    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 轉(zhuǎn)化為 RGB值.convertMatrix->matrix * (YUV + convertMatrix->offset)
    float3 rgb = convertMatrix->matrix * (yuv + convertMatrix->offset);

    // 返回顏色值
    return float4(rgb, 1.0);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容