日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Metal之实现视频采集与实时渲染

發(fā)布時間:2024/5/21 编程问答 63 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Metal之实现视频采集与实时渲染 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

一、視頻渲染實現(xiàn)思路

① 思路說明
  • 通過AVFoundation進(jìn)行視頻數(shù)據(jù)的采集,并將采集到的原始數(shù)據(jù)存儲到CMSampleBufferRef中,即視頻幀數(shù)據(jù)(視頻幀其實本質(zhì)也是一張圖片)。
  • 通過CoreVideo將CMSampleBufferRef中存儲的圖像數(shù)據(jù),轉(zhuǎn)換為Metal可以直接使用的紋理。
  • 將Metal紋理進(jìn)行渲染,并即刻顯示到屏幕上。
② 思路實現(xiàn)
  • 在實際的開發(fā)應(yīng)用中,AVFoundation 提供了一個 layer,即AVCaptureVideoPreviewLayer 預(yù)覽層,我們可以使用預(yù)覽層直接預(yù)覽視頻采集后的即可渲染,用于直接實現(xiàn)上面思路中的第二步和第三步。
  • 根據(jù)官方文檔之AVCaptureVideoPreviewLayer說明,AVCaptureVideoPreviewLayer 是 CALayer 的子類,用于在輸入設(shè)備捕獲視頻時顯示視頻,此預(yù)覽圖層與捕獲會話結(jié)合使用,主要有以下三步:
    • 創(chuàng)建預(yù)覽層對象;
    • 將預(yù)覽層與captureSession鏈接;
    • 將預(yù)覽層加到view的子layer中。
// 創(chuàng)建一個預(yù)覽層let previewLayer = AVCaptureVideoPreviewLayer()// 將預(yù)覽層與捕獲會話連接previewLayer.session = captureSession// 將預(yù)覽圖層添加到視圖的圖層層次結(jié)構(gòu)中view.layer.addSublayer(previewLayer)
③ 整體流程
  • viewDidLoad函數(shù):初始化Metal和視頻采集的準(zhǔn)備工作;
  • MTKViewDelegate協(xié)議方法:視頻采集數(shù)據(jù)轉(zhuǎn)換為紋理;
  • AVCaptureVideoDataOutputSampleBufferDelegate協(xié)議方法:將采集轉(zhuǎn)換后的紋理渲染到屏幕上。

二、初始化 Metal 和視頻采集的準(zhǔn)備工作

① 整體流程如下:

② setupMetal函數(shù)
  • 初始化MTKView,用于顯示視頻采集數(shù)據(jù)轉(zhuǎn)換后的紋理;
self.mtkView = [[MTKView alloc] initWithFrame:self.view.bounds device:MTLCreateSystemDefaultDevice()];[self.view insertSubview:self.mtkView atIndex:0];self.mtkView.delegate = self;
  • 創(chuàng)建命令隊列:通過MTKView中的device創(chuàng)建;
self.commandQueue = [self.mtkView.device newCommandQueue];
  • 設(shè)置MTKView的讀寫操作 & 創(chuàng)建紋理緩沖區(qū):
    • MTKView中的framebufferOnly屬性,默認(rèn)的幀緩存是只讀的即YES,由于view需要顯示紋理,所以需要該屬性改為可讀寫即NO;
    • 通過CVMetalTextureCacheCreate方法創(chuàng)建CoreVideo中的metal紋理緩存區(qū),因為采集的視頻數(shù)據(jù)是通過CoreVideo轉(zhuǎn)換為metal紋理的,主要的用于存儲轉(zhuǎn)換后的metal紋理;
//注意: 在初始化MTKView 的基本操作以外. 還需要多下面2行代碼./*1. 設(shè)置MTKView 的drawable 紋理是可讀寫的(默認(rèn)是只讀);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)存分配器.默認(rèn)即可.NULL參數(shù)2: cacheAttributes 緩存區(qū)行為字典.默認(rèn)為NULL參數(shù)3: metalDevice參數(shù)4: textureAttributes 緩存創(chuàng)建紋理選項的字典. 使用默認(rèn)選項NULL參數(shù)5: cacheOut 返回時,包含新創(chuàng)建的紋理緩存。*/CVMetalTextureCacheCreate(NULL, NULL, self.mtkView.device, NULL, &_textureCache);
③ setupCaptureSession函數(shù)
  • 設(shè)置AVCaptureSession & 視頻采集的分辨率;
// 設(shè)置AVCaptureSession & 視頻采集的分辨率self.mCaptureSession = [[AVCaptureSession alloc] init];self.mCaptureSession.sessionPreset = AVCaptureSessionPreset1920x1080;
  • 創(chuàng)建串行隊列:串行隊列創(chuàng)建的目的在于處理captureSession的交互時,不會影響主隊列,在蘋果官方文檔中有如下圖示,表示captureSession是如何管理設(shè)備的輸入 & 輸出,以及與主隊列之間的關(guān)系,session管理輸入和輸出圖示如下:

  • 關(guān)于串行隊列在官方文檔中的描述:

  • 實現(xiàn)邏輯如下:
self.mProcessQueue = dispatch_queue_create("mProcessQueue", DISPATCH_QUEUE_SERIAL);
  • 設(shè)置輸入設(shè)備
    • 獲取后置攝像頭設(shè)備AVCaptureDevice:通過獲取設(shè)備數(shù)組,循環(huán)判斷找到后置攝像頭,將后置攝像頭設(shè)備為當(dāng)前的輸入設(shè)備;
    • 通過攝像頭設(shè)備創(chuàng)建AVCaptureDeviceInput:將AVCaptureDevice 轉(zhuǎn)換為 AVCaptureDeviceInput,主要是因為 AVCaptureSession 無法直接使用 AVCaptureDevice,所以需要將device轉(zhuǎn)換為deviceInput;
    • 輸入設(shè)備添加到captureSession中:在添加之前,需要通過captureSession的canAddInput函數(shù)判斷是否可以添加輸入設(shè)備,如果可以,則通過session的addInput函數(shù)添加輸入設(shè)備;
// 獲取攝像頭設(shè)備(前置/后置攝像頭設(shè)備)NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];AVCaptureDevice *inputCamera = nil;// 循環(huán)設(shè)備數(shù)組,找到后置攝像頭.設(shè)置為當(dāng)前inputCamerafor (AVCaptureDevice *device in devices) {if ([device position] == AVCaptureDevicePositionBack) {// 拿到后置攝像頭inputCamera = device;}}// 將AVCaptureDevice 轉(zhuǎn)換為 AVCaptureDeviceInput,即輸入// AVCaptureSession 無法直接使用 AVCaptureDevice,所喲需要將device轉(zhuǎn)換為deviceInputself.mCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:inputCamera error:nil];// 將設(shè)備添加到captureSession中,需要先判斷能否添加輸入if ([self.mCaptureSession canAddInput:self.mCaptureDeviceInput]) {[self.mCaptureSession addInput:self.mCaptureDeviceInput];}
  • 設(shè)置輸出設(shè)備
    • 創(chuàng)建AVCaptureVideoDataOutput對象,即輸出設(shè)備;
    • 設(shè)置輸出設(shè)備的setAlwaysDiscardsLateVideoFrames屬性(表示視頻幀延時使是否丟棄數(shù)據(jù))為NO:
      • YES:處理現(xiàn)有幀的調(diào)度隊列,在captureOutput:didOutputSampleBuffer:FromConnection:Delegate方法中被阻止時,對象會立即丟棄捕獲的幀;
      • NO:在丟棄新幀之前,允許委托有更多的時間處理舊幀,但這樣可能會內(nèi)存增加
    • 設(shè)置輸出設(shè)備的setVideoSettings屬性(即像素格式),表示每一個像素點顏色保存的格式,且設(shè)置的格式是BGRA,而不是YUV,主要是為了避免Shader轉(zhuǎn)換,如果使用了YUV格式,就需要編寫shader來進(jìn)行顏色格式轉(zhuǎn)換;
    • 設(shè)置輸出設(shè)備的視頻捕捉輸出的delegate;
    • 將輸出設(shè)備添加到captureSession中;
// 創(chuàng)建AVCaptureVideoDataOutput對象,即輸出 & 設(shè)置輸出相關(guān)屬性self.mCaptureDeviceOutput = [[AVCaptureVideoDataOutput alloc] init];/*設(shè)置視頻幀延遲到底時是否丟棄數(shù)據(jù)YES: 處理現(xiàn)有幀的調(diào)度隊列在captureOutput:didOutputSampleBuffer:FromConnection:Delegate方法中被阻止時,對象會立即丟棄捕獲的幀。NO: 在丟棄新幀之前,允許委托有更多的時間處理舊幀,但這樣可能會內(nèi)存增加.*/// 視頻幀延遲是否需要丟幀[self.mCaptureDeviceOutput setAlwaysDiscardsLateVideoFrames:NO];// 設(shè)置像素格式:每一個像素點顏色保存的格式// 這里設(shè)置格式為BGRA,而不用YUV的顏色空間,避免使用Shader轉(zhuǎn)換,如果使用YUV格式,需要編寫shade來進(jìn)行顏色格式轉(zhuǎn)換// 注意:這里必須和后面CVMetalTextureCacheCreateTextureFromImage 保存圖像像素存儲格式保持一致.否則視頻會出現(xiàn)異常現(xiàn)象.[self.mCaptureDeviceOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];// 設(shè)置視頻捕捉輸出的代理方法:將采集的視頻數(shù)據(jù)輸出[self.mCaptureDeviceOutput setSampleBufferDelegate:self queue:self.mProcessQueue];// 添加輸出,即添加到captureSession中if ([self.mCaptureSession canAddOutput:self.mCaptureDeviceOutput]) {[self.mCaptureSession addOutput:self.mCaptureDeviceOutput];}
  • 輸入與輸出鏈接 & 設(shè)置視頻輸出方向:通過AVCaptureConnection鏈接輸入和輸出,并設(shè)置connect的視頻輸出方向,即設(shè)置videoOrientation屬性;
// 輸入與輸出鏈接AVCaptureConnection *connection = [self.mCaptureDeviceOutput connectionWithMediaType:AVMediaTypeVideo];// 設(shè)置視頻輸出方向// 注意: 一定要設(shè)置視頻方向.否則視頻會是朝向異常的.[connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
  • 輸入與輸出鏈接 & 設(shè)置視頻輸出方向:通過AVCaptureConnection鏈接輸入和輸出,并設(shè)置connect的視頻輸出方向,即設(shè)置videoOrientation屬性;
// 輸入與輸出鏈接AVCaptureConnection *connection = [self.mCaptureDeviceOutput connectionWithMediaType:AVMediaTypeVideo];// 設(shè)置視頻輸出方向// 注意: 一定要設(shè)置視頻方向.否則視頻會是朝向異常的[connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
  • 開始捕捉,即開始視頻采集,也可以通過一個按鈕來控制視頻采集的開始與停止
    • startRunning:開啟捕捉
    • stopRunning:停止捕捉
// 開始捕捉[self.mCaptureSession startRunning];

三 、AVCaptureVideoDataOutputSampleBufferDelegate協(xié)議

① 整體流程
  • 在視頻采集的同時,采集到的視頻數(shù)據(jù),即視頻幀會自動回調(diào)視頻采集回調(diào)方法captureOutput:didOutputSampleBuffer:fromConnection:,在該方法中處理采集到的原始視頻數(shù)據(jù),將其轉(zhuǎn)換為metal紋理;
  • didOutputSampleBuffer代理方法:主要是獲取視頻的幀數(shù)據(jù),將其轉(zhuǎn)換為metal紋理,函數(shù)流程如下:

② 流程分解說明
  • 從sampleBuffer中獲取位圖:通過CMSampleBufferGetImageBuffer函數(shù)從sampleBuffer形參中獲取視頻像素緩存區(qū)對象,即視頻幀數(shù)據(jù),平常所說的位圖:
// 從sampleBuffer 獲取視頻像素緩存區(qū)對象,即獲取位圖CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
  • 獲取捕捉視頻幀的寬高:通過CoreVideo中的CVPixelBufferGetWidth和CVPixelBufferGetHeight函數(shù)獲取寬高;
size_t width = CVPixelBufferGetWidth(pixelBuffer);size_t height = CVPixelBufferGetHeight(pixelBuffer);
  • 將位圖轉(zhuǎn)換為metal紋理:
    • 通過CVMetalTextureRef創(chuàng)建臨時紋理;
    • 通過CVMetalTextureCacheCreateTextureFromImage函數(shù)創(chuàng)建metal紋理緩沖區(qū),賦值給臨時紋理;
    • 判斷臨時紋理是否創(chuàng)建成功,如果臨時紋理創(chuàng)建成功,則繼續(xù)往下執(zhí)行;
    • 設(shè)置MTKView中的drawableSize屬性,即表示可繪制紋理的大小;
    • 通過CVMetalTextureGetTexture函數(shù),獲取紋理緩沖區(qū)的metal紋理對象;
    • 釋放臨時紋理;
// 將位圖轉(zhuǎn)換為紋理// 方法來自CoreVideo/* 根據(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ū)對象參數(shù)3: sourceImage 視頻圖像緩沖區(qū)參數(shù)4: textureAttributes 紋理參數(shù)字典.默認(rèn)為NULL參數(shù)5: pixelFormat 圖像緩存區(qū)數(shù)據(jù)的Metal 像素格式常量.注意如果MTLPixelFormatBGRA8Unorm和攝像頭采集時設(shè)置的顏色格式不一致,則會出現(xiàn)圖像異常的情況;參數(shù)6: width,紋理圖像的寬度(像素)參數(shù)7: height,紋理圖像的高度(像素)參數(shù)8: planeIndex 顏色通道.如果圖像緩沖區(qū)是平面的,則為映射紋理數(shù)據(jù)的平面索引。對于非平面圖像緩沖區(qū)忽略。參數(shù)9: textureOut,返回時,返回創(chuàng)建的Metal紋理緩沖區(qū)。*/// 創(chuàng)建臨時紋理CVMetalTextureRef tmpTexture = NULL;CVReturn status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, self.textureCache, pixelBuffer, NULL, MTLPixelFormatBGRA8Unorm, width, height, 0, &tmpTexture);// 判斷 tmpTexture 是否創(chuàng)建成功if (status == kCVReturnSuccess) {// 創(chuàng)建成功// 設(shè)置可繪制紋理的大小self.mtkView.drawableSize = CGSizeMake(width, height);// 返回紋理緩沖區(qū)的metal紋理對象self.texture = CVMetalTextureGetTexture(tmpTexture);// 使用完畢,釋放tmptextureCFRelease(tmpTexture);}

四、MTKViewDelegate協(xié)議

① 說明
  • 將獲取的metal紋理即刻渲染并顯示到屏幕上,這里是通過MTKViewDelegate協(xié)議的drawInMTKView代理方法渲染并顯示。
  • drawInMTKView代理方法
    MTKView默認(rèn)的幀速率與屏幕刷新頻率一致,所以每當(dāng)屏幕刷新時,都會回調(diào) 視頻采集方法和視圖渲染方法,以下是視圖渲染方法執(zhí)行流程:

② 具體步驟
  • 判斷紋理是否獲取成功:即紋理不為空,如果紋理為空,則沒必要執(zhí)行視圖渲染流程;
  • 通過commandQueue創(chuàng)建commandBuffer命令緩存區(qū);
  • 將MTKView的紋理作為目標(biāo)渲染紋理,即獲取view中紋理對象
  • 設(shè)置高斯模糊濾鏡:
    • MetalPerformanceShaders是Metal的一個集成庫,有一些濾鏡處理的Metal實現(xiàn);
    • 此時的濾鏡就等價于Metal中的MTLRenderCommandEncoder渲染命令編碼器,類似于GLSL中program;
    • 高斯模糊濾鏡在渲染時,會觸發(fā)離屏渲染,且其中的sigma值設(shè)置的越高,圖像越模糊;
// 設(shè)置濾鏡(Metal封裝了一些濾鏡)// 高斯模糊 渲染時,會觸發(fā) 離屏渲染/*MetalPerformanceShaders是Metal的一個集成庫,有一些濾鏡處理的Metal實現(xiàn);MPSImageGaussianBlur 高斯模糊處理;*/// 創(chuàng)建高斯濾鏡處理filter// 注意:sigma值可以修改,sigma值越高圖像越模糊;MPSImageGaussianBlur *filter = [[MPSImageGaussianBlur alloc] initWithDevice:self.mtkView.device sigma:5];// MPSImageGaussianBlur以一個Metal紋理作為輸入,以一個Metal紋理作為輸出;// 輸入:攝像頭采集的圖像 self.texture// 輸出:創(chuàng)建的紋理 drawingTexture(其實就是view.currentDrawable.texture)// filter等價于Metal中的MTLRenderCommandEncoder 渲染命令編碼器,類似于GLSL中的program[filter encodeToCommandBuffer:commandBuffer sourceTexture:self.texture destinationTexture:drawingTexture];
  • 將獲取的紋理顯示到屏幕上;
  • 將commandBuffer通過commit提交給GPU;
  • 清空當(dāng)前紋理,為下一次紋理數(shù)據(jù)讀取做準(zhǔn)備;
    如果不清空,也是可以的,下一次的紋理數(shù)據(jù)會將上次的數(shù)據(jù)覆蓋;
// 展示顯示的內(nèi)容[commandBuffer presentDrawable:view.currentDrawable];// 提交命令[commandBuffer commit];// 清空當(dāng)前紋理,準(zhǔn)備下一次的紋理數(shù)據(jù)讀取// 如果不清空,也是可以的,下一次的紋理數(shù)據(jù)會將上次的數(shù)據(jù)覆蓋self.texture = NULL;

五、總結(jié)

① 視頻采集流程
  • 設(shè)置session;
  • 創(chuàng)建串行隊列;
  • 設(shè)置輸入設(shè)備;
  • 設(shè)置輸出設(shè)備;
  • 輸入與輸出鏈接;
  • 設(shè)置視頻輸出方向;
  • 開始捕捉,即開始視頻采集;
  • AVCaptureVideoDataOutputSampleBufferDelegate協(xié)議處理采集后的視頻數(shù)據(jù);

② 如何判斷采集的數(shù)據(jù)是音頻還是視頻
  • 通過AVCaptureConnection判斷
    • 視頻:包含視頻輸入設(shè)備 & 視頻輸出設(shè)備,通過AVCaptureConnection鏈接起來
    • 音頻:包含音頻輸入設(shè)備 & 音頻輸出設(shè)備,同樣通過AVCaptureConnection鏈接起來
    • 如果需要判斷當(dāng)前采集的輸出是視頻還是音頻,需要將connect對象設(shè)置為全局變量,然后在采集回調(diào)方法captureOutput:didOutputSampleBuffer:fromConnection:中判斷全局的connection 是否等于代理方法參數(shù)中的coneection ,如果相等,就是視頻,反之是音頻;
  • 通過AVCaptureOutput判斷
    在采集回調(diào)方法captureOutput:didOutputSampleBuffer:fromConnection:中判斷output形參的類型,如果是AVCaptureVideoDataOutput 類型則是視頻,反之,是音頻。

總結(jié)

以上是生活随笔為你收集整理的Metal之实现视频采集与实时渲染的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。