Metal之實時渲染攝像頭畫面

我們知道Metal可以完成圖片渲染秒裕,我們也知道視頻是由一幀一幀的圖片組成的几蜻。今天我們將使用Metal來完成攝像頭的實時顯示体斩。

實現(xiàn)原理

1 利用AVFoundation做圖像采集
2 將AVFoundation的采集回調(diào)中的CMSampleBufferRef對象轉(zhuǎn)換成紋理對象
3 我們將拿到的紋理對象通過Metal實時渲染到屏幕上(MTKView)

Metal實時渲染攝像頭畫面圖解.png

配置Metal

  • 配置Metal圖解


    Metal配置圖解.png
  • 實現(xiàn)代碼
    //1.創(chuàng)建MTKView
    self.mtkView = [[MTKView alloc] initWithFrame:self.view.bounds];
    self.mtkView.device = MTLCreateSystemDefaultDevice();
    [self.view insertSubview:self.mtkView atIndex:0];
    self.mtkView.delegate = self;
    
    //2.創(chuàng)建命令隊列.
    self.commandQueue = [self.mtkView.device newCommandQueue];
    
    //3.注意: 在初始化MTKView 的基本操作以外. 還需要多下面2行代碼.
    /*
     1. 設置MTKView 的drawable 紋理是可讀寫的(默認是只讀);
     2. 創(chuàng)建CVMetalTextureCacheRef _textureCache; 這是Core Video的Metal紋理緩存
     */
    //允許讀寫操作
    self.mtkView.framebufferOnly = NO;
    /*
     CVMetalTextureCacheCreate(CFAllocatorRef  allocator,
     CFDictionaryRef cacheAttributes,
     id <MTLDevice>  metalDevice,
     CFDictionaryRef  textureAttributes,
     CVMetalTextureCacheRef * CV_NONNULL cacheOut )
     
     功能: 創(chuàng)建紋理緩存區(qū)
     參數(shù)1: allocator 內(nèi)存分配器.默認即可.NULL
     參數(shù)2: cacheAttributes 緩存區(qū)行為字典.默認為NULL
     參數(shù)3: metalDevice
     參數(shù)4: textureAttributes 緩存創(chuàng)建紋理選項的字典. 使用默認選項NULL
     參數(shù)5: cacheOut 返回時弧烤,包含新創(chuàng)建的紋理緩存暇昂。
     
     */
    CVMetalTextureCacheCreate(NULL, NULL, self.mtkView.device, NULL, &_textureCache);

配置視頻采集

  • 配置步驟圖解


    配置視頻采集圖解.png
  • 具體代碼如下:
    //創(chuàng)建mCaptureSession
    self.mCaptureSession = [[AVCaptureSession alloc] init];
    //設置視頻采集的分辨率
    self.mCaptureSession.sessionPreset = AVCaptureSessionPreset1920x1080;
  
    //創(chuàng)建串行隊列
    self.mProcessQueue = dispatch_queue_create("mProcessQueue", DISPATCH_QUEUE_SERIAL);
   
    //獲取攝像頭設備(前置/后置攝像頭設備)
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    AVCaptureDevice *inputCamera = nil;
    //循環(huán)設備數(shù)組,找到后置攝像頭.設置為當前inputCamera
    for (AVCaptureDevice *device in devices) {
        if ([device position] == AVCaptureDevicePositionBack) {
            inputCamera = device;
        }
    }
    
    //將AVCaptureDevice 轉(zhuǎn)換為AVCaptureDeviceInput
    self.mCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:inputCamera error:nil];
    
    //將設備添加到mCaptureSession中
    if ([self.mCaptureSession canAddInput:self.mCaptureDeviceInput]) {
        [self.mCaptureSession addInput:self.mCaptureDeviceInput];
    }
    
    //6.創(chuàng)建AVCaptureVideoDataOutput 對象
    self.mCaptureDeviceOutput = [[AVCaptureVideoDataOutput alloc] init];
    
    /*設置視頻幀延遲到底時是否丟棄數(shù)據(jù).
     YES: 處理現(xiàn)有幀的調(diào)度隊列在captureOutput:didOutputSampleBuffer:FromConnection:Delegate方法中被阻止時从铲,對象會立即丟棄捕獲的幀。
     NO: 在丟棄新幀之前吉嫩,允許委托有更多的時間處理舊幀自娩,但這樣可能會內(nèi)存增加.
     */
    [self.mCaptureDeviceOutput setAlwaysDiscardsLateVideoFrames:NO];
    
    //這里設置格式為BGRA忙迁,而不用YUV的顏色空間碎乃,避免使用Shader轉(zhuǎn)換
    //注意:這里必須和后面CVMetalTextureCacheCreateTextureFromImage 保存圖像像素存儲格式保持一致.否則視頻會出現(xiàn)異常現(xiàn)象.
    [self.mCaptureDeviceOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];
    
    //設置視頻捕捉輸出的代理方法
    [self.mCaptureDeviceOutput setSampleBufferDelegate:self queue:self.mProcessQueue];
    
    //添加輸出
    if ([self.mCaptureSession canAddOutput:self.mCaptureDeviceOutput]) {
        [self.mCaptureSession addOutput:self.mCaptureDeviceOutput];
    }
    
    //輸入與輸出鏈接
    AVCaptureConnection *connection = [self.mCaptureDeviceOutput connectionWithMediaType:AVMediaTypeVideo];
    
    //設置視頻方向
    //注意: 一定要設置視頻方向.否則視頻會是朝向異常的.
    [connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
    
    //開始捕捉
    [self.mCaptureSession startRunning];

AVFundation采集回調(diào)方法

拿到CMSampleBufferRef之后的處理.png

具體代碼:

    //1.從sampleBuffer 獲取視頻像素緩存區(qū)對象
    CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
   
    //2.獲取捕捉視頻的寬和高
    size_t width = CVPixelBufferGetWidth(pixelBuffer);
    size_t height = CVPixelBufferGetHeight(pixelBuffer);
    
    /*3. 根據(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)存分配器,默認kCFAllocatorDefault
     參數(shù)2: textureCache 紋理緩存區(qū)對象
     參數(shù)3: sourceImage 視頻圖像緩沖區(qū)
     參數(shù)4: textureAttributes 紋理參數(shù)字典.默認為NULL
     參數(shù)5: pixelFormat 圖像緩存區(qū)數(shù)據(jù)的Metal 像素格式常量.注意如果MTLPixelFormatBGRA8Unorm和攝像頭采集時設置的顏色格式不一致,則會出現(xiàn)圖像異常的情況嵌言;
     參數(shù)6: width,紋理圖像的寬度(像素)
     參數(shù)7: height,紋理圖像的高度(像素)
     參數(shù)8: planeIndex.如果圖像緩沖區(qū)是平面的,則為映射紋理數(shù)據(jù)的平面索引及穗。對于非平面圖像緩沖區(qū)忽略。
     參數(shù)9: textureOut,返回時埂陆,返回創(chuàng)建的Metal紋理緩沖區(qū)苛白。
     
     // Mapping a BGRA buffer:
     CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache, pixelBuffer, NULL, MTLPixelFormatBGRA8Unorm, width, height, 0, &outTexture);
     
     // Mapping the luma plane of a 420v buffer:
     CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache, pixelBuffer, NULL, MTLPixelFormatR8Unorm, width, height, 0, &outTexture);
     
     // Mapping the chroma plane of a 420v buffer as a source texture:
     CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache, pixelBuffer, NULL, MTLPixelFormatRG8Unorm width/2, height/2, 1, &outTexture);
     
     // Mapping a yuvs buffer as a source texture (note: yuvs/f and 2vuy are unpacked and resampled -- not colorspace converted)
     CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache, pixelBuffer, NULL, MTLPixelFormatGBGR422, width, height, 1, &outTexture);
     
     */
    CVMetalTextureRef tmpTexture = NULL;
    CVReturn status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, self.textureCache, pixelBuffer, NULL, MTLPixelFormatBGRA8Unorm, width, height, 0, &tmpTexture);
    
    //4.判斷tmpTexture 是否創(chuàng)建成功
    if(status == kCVReturnSuccess)
    {
        //5.設置可繪制紋理的當前大小焚虱。
        self.mtkView.drawableSize = CGSizeMake(width, height);
        //6.返回紋理緩沖區(qū)的Metal紋理對象购裙。
        self.texture = CVMetalTextureGetTexture(tmpTexture);
        //7.使用完畢,則釋放tmpTexture
        CFRelease(tmpTexture);
    }

MTKView的代理方法

  • 步驟圖解


    drawInMTKView方法中的處理.png
  • 具體代碼
    //1.判斷是否獲取了AVFoundation 采集的紋理數(shù)據(jù)
    if (self.texture) {
        
        //2.創(chuàng)建指令緩沖
        id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];
        
        //3.將MTKView 作為目標渲染紋理
        id<MTLTexture> drawingTexture = view.currentDrawable.texture;
        
        //4.設置濾鏡
        /*
         MetalPerformanceShaders是Metal的一個集成庫私股,有一些濾鏡處理的Metal實現(xiàn);
         MPSImageGaussianBlur 高斯模糊處理;
         */
       
        //創(chuàng)建高斯濾鏡處理filter
        //注意:sigma值可以修改黄娘,sigma值越高圖像越模糊;
        MPSImageGaussianBlur *filter = [[MPSImageGaussianBlur alloc] initWithDevice:self.mtkView.device sigma:1];
        
        //5.MPSImageGaussianBlur以一個Metal紋理作為輸入誓焦,以一個Metal紋理作為輸出杂伟;
        //輸入:攝像頭采集的圖像 self.texture
        //輸出:創(chuàng)建的紋理 drawingTexture(其實就是view.currentDrawable.texture)
        [filter encodeToCommandBuffer:commandBuffer sourceTexture:self.texture destinationTexture:drawingTexture];
        
        //6.展示顯示的內(nèi)容
        [commandBuffer presentDrawable:view.currentDrawable];
        
        //7.提交命令
        [commandBuffer commit];
        
        //8.清空當前紋理,準備下一次的紋理數(shù)據(jù)讀取.
        self.texture = NULL;
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末观话,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子帽驯,更是在濱河造成了極大的恐慌,老刑警劉巖利凑,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牌借,死亡現(xiàn)場離奇詭異膨报,居然都是意外死亡院领,警方通過查閱死者的電腦和手機比然,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門湾笛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嚎研,“玉大人嘉赎,你說我怎么就攤上這事公条×认” “怎么了传黄?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長识埋。 經(jīng)常有香客問我窒舟,道長银还,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任苍苞,我火速辦了婚禮,結(jié)果婚禮上冈欢,老公的妹妹穿的比我還像新娘凑耻。我一直安慰自己香浩,他們只是感情好邻吭,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著畸写,像睡著了一般。 火紅的嫁衣襯著肌膚如雪幢尚。 梳的紋絲不亂的頭發(fā)上尉剩,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機與錄音朗鸠,去河邊找鬼。 笑死沟启,一個胖子當著我的面吹牛芽卿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播筷转,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼阴绢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起腺兴,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎栈拖,沒想到半個月后涩哟,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體潜腻,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡精钮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了檀夹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片策橘。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡炸渡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出丽已,到底是詐尸還是另有隱情蚌堵,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布沛婴,位于F島的核電站吼畏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏嘁灯。R本人自食惡果不足惜羹奉,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一铺然、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧良哲,春花似錦滓技、人聲如沸叠必。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酣胀。三九已至,卻和暖如春酝豪,著一層夾襖步出監(jiān)牢的瞬間揉阎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工捆探, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留缸沃,地道東北人吨枉。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子前计,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355