AVCapture之4——NSOpenGLView

想要高效的進行界面刷新,OpenGL/硬件加速是必須的。最近我在研究OpenGL的過程中族展,被OpenGL的API饶套、Shader、GSLS等燒腦得不要不要的额衙。也不怪它,OpenGL本來就是為3D動畫設計的,一上來肯定高大上贱鼻。
網(wǎng)絡上有不少OpenGL ES 2.0視頻render的開源實現(xiàn),蘋果自家的GLCameraRipple示例也非常棒滋将,但是鮮有OpenGL的實現(xiàn)邻悬。OpenGL渲染紋理,CoreImage也可以做得到随闽。

蘋果有個Demo中有一個VideoCIView父丰,這個類基本實現(xiàn)了將一個CIImage繪制到NSOpenGLView中。

VideoCIView繼承自NSOpenGLView掘宪,主要是想利用顯示區(qū)域變化的事件回調(diào)蛾扇。還有一點要注意攘烛,NSOpenGLView不能有subview。

+ (NSOpenGLPixelFormat *)defaultPixelFormat
{
    static NSOpenGLPixelFormat *pf;
    
    if (pf == nil)
    {
        // You must make sure that the pixel format of the context does not
        // have a recovery renderer is important. Otherwise CoreImage may not be able to
        // create contexts that share textures with this context.
        
        static const NSOpenGLPixelFormatAttribute attr[] = {
            NSOpenGLPFAAccelerated,
            NSOpenGLPFANoRecovery,
            NSOpenGLPFAColorSize, 32,
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4
            NSOpenGLPFAAllowOfflineRenderers,
#endif
            0
        };
        
        pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:(void *)&attr];
    }
    
    return pf;
}

NSOpenGLPixelFormat是每個NSOpenGLView初始化所必需的镀首,這種已C數(shù)組作為attribute傳入到OC中的做法比較少見坟漱。attribute分兩種類型:1、BOOL更哄;2芋齿、帶整數(shù)。此函數(shù)主要是設置OpenGL的一些參數(shù)竖瘾,實現(xiàn)大同小異沟突。

- (void)prepareOpenGL
{
    GLint parm = 1;
    
    //  Set the swap interval to 1 to ensure that buffers swaps occur only during the vertical retrace of the monitor.
    
    [[self openGLContext] setValues:&parm forParameter:NSOpenGLCPSwapInterval];
    
    // To ensure best performance, disbale everything you don't need.
    
    glDisable (GL_ALPHA_TEST);
    glDisable (GL_DEPTH_TEST);
    glDisable (GL_SCISSOR_TEST);
    glDisable (GL_BLEND);
    glDisable (GL_DITHER);
    glDisable (GL_CULL_FACE);
    glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glDepthMask (GL_FALSE);
    glStencilMask (0);
    glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
    glHint (GL_TRANSFORM_HINT_APPLE, GL_FASTEST);
    _needsReshape = YES;
}

當OpenGL初始化完成當前context,就會調(diào)用一次此函數(shù)捕传,姑且認為它就是viewDidLoad吧惠拭。Apple這里關閉了很多不需要的參數(shù)。

// Called when the user scrolls, moves, or resizes the view.
- (void)reshape
{
    // Resets the viewport on the next draw operation.
    _needsReshape = YES;
}

- (void)updateMatrices
{
    NSRect  visibleRect = [self visibleRect];
    NSRect  mappedVisibleRect = NSIntegralRect([self convertRect: visibleRect toView: [self enclosingScrollView]]);
    
    [[self openGLContext] update];
    
    // Install an orthographic projection matrix (no perspective)
    // with the origin in the bottom left and one unit equal to one device pixel.
    
    glViewport (0, 0,mappedVisibleRect.size.width, mappedVisibleRect.size.height);
    
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    glOrtho(visibleRect.origin.x,
            visibleRect.origin.x + visibleRect.size.width,
            visibleRect.origin.y,
            visibleRect.origin.y + visibleRect.size.height,
            -1, 1);
    
    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();
    _needsReshape = NO;
}

當外部窗口大小發(fā)生變化時庸论,會調(diào)用此方法(這也是不選NSOpenGLLayer的原因)职辅。窗口大小變化后,沒有直接修改glViewport聂示,而是設置_needsReshap = YES域携!這算是OpenGL的一個通用設計模式吧——所有繪制操作都在render函數(shù)里完成,且render函數(shù)不重入(我瞎BB的)鱼喉。OpenGL繪制不需要一定在主線程秀鞭,至于render函數(shù)嘛,一般都是用CADisplayLink驅(qū)動扛禽,我們這個工程有onCapture回調(diào)锋边,所以省了。

- (void)render
{
    NSRect      frame = [self bounds];
    
    [[self openGLContext] makeCurrentContext];
    
    if (_needsReshape)
    {
        [self updateMatrices];
        glClear (GL_COLOR_BUFFER_BIT);
    }
    
    CGRect      imageRect = [_image extent];
    CGRect      destRect = *((CGRect*)&frame);
    
    [[self ciContext] drawImage:_image inRect:destRect fromRect:imageRect];
    
    // Flush the OpenGL command stream. If the view is double-buffered
    // you should  replace  this call with [[self openGLContext]
    
    glFlush ();
    
}

- (CIContext*)ciContext
{
    // Allocate a CoreImage rendering context using the view's OpenGL
    // context as its destination if none already exists.
    // You must do this before sending any queries to the CIContext.
    
    if (_context == nil)
    {
        [[self openGLContext] makeCurrentContext];
        NSOpenGLPixelFormat *pf;
        
        pf = [self pixelFormat];
        if (pf == nil)
            pf = [[self class] defaultPixelFormat];
        
        _context = [[CIContext contextWithCGLContext: CGLGetCurrentContext()
                                         pixelFormat: [pf CGLPixelFormatObj] options: nil] retain];
    }
    return _context;
}

真正的render就干兩件事

  1. 可視區(qū)域變化编曼?更新viewport和映射關系
  2. 把CIImage繪制到OpenGL中

第2步前需要先得到用于CoreImage繪制的CIContext豆巨。繪制函數(shù)就一行,drawImage掐场。當然往扔,這個繪制是在GPU完成的,速度非承芑В快萍膛。

最后,通過setImage來驅(qū)動render

- (void)setImage:(CIImage *)image
{
    if (_image != image)
    {
        [_image release];
        _image = [image retain];
    }
    [self render];
}

最后一步關鍵點嚷堡,獲得CIImage卦羡。通過CMSampleBuffer得到CVImageBuffer,然后再通過CVImageBuffer得到CIImage

    CVImageBufferRef videoFrame = CMSampleBufferGetImageBuffer(sampleBuffer);
    CIImage* image = [CIImage imageWithCVImageBuffer:videoFrame];

+[CIImage imageWithCVImageBuffer:]并不總是能成功,這與采集的圖像格式有關绿饵。

整個過程NSOpenGLView繁雜了點,iOS上的GLKView用起來要簡單許多瓶颠。
最后測試下來拟赊,CIContext的方式CPU占用率約7%,比AVSampleBufferDisplayLayer稍差粹淋。原因主要是CVImageBufferRef -> CIImage占用了許多時間吸祟,而繪制過程還是相當?shù)母咝А?/p>

我是比較喜歡這種渲染方式,它既利用到硬件加速桃移,而且CIImage還要好多濾鏡可以玩屋匕。最重要的,這種實現(xiàn)方案比較簡單借杰。


參考鏈接:
Stupid Video Tricks (CocoaConf DC, March 2014)

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末过吻,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蔗衡,更是在濱河造成了極大的恐慌纤虽,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绞惦,死亡現(xiàn)場離奇詭異逼纸,居然都是意外死亡,警方通過查閱死者的電腦和手機济蝉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門杰刽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人王滤,你說我怎么就攤上這事贺嫂。” “怎么了淑仆?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵涝婉,是天一觀的道長。 經(jīng)常有香客問我蔗怠,道長墩弯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任寞射,我火速辦了婚禮渔工,結果婚禮上,老公的妹妹穿的比我還像新娘桥温。我一直安慰自己引矩,他們只是感情好,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著旺韭,像睡著了一般氛谜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上区端,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天值漫,我揣著相機與錄音,去河邊找鬼织盼。 笑死杨何,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的沥邻。 我是一名探鬼主播危虱,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼唐全!你這毒婦竟也來了埃跷?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤芦瘾,失蹤者是張志新(化名)和其女友劉穎捌蚊,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體近弟,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡缅糟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了祷愉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窗宦。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖二鳄,靈堂內(nèi)的尸體忽然破棺而出赴涵,到底是詐尸還是另有隱情,我是刑警寧澤订讼,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布髓窜,位于F島的核電站,受9級特大地震影響欺殿,放射性物質(zhì)發(fā)生泄漏寄纵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一脖苏、第九天 我趴在偏房一處隱蔽的房頂上張望程拭。 院中可真熱鬧,春花似錦棍潘、人聲如沸恃鞋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恤浪。三九已至畅哑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間资锰,已是汗流浹背敢课。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留绷杜,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓濒募,卻偏偏與公主長得像鞭盟,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瑰剃,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 許多UIView的子類齿诉,如一個UIButton或一個UILabel,它們知道怎么繪制自己晌姚。遲早粤剧,你也將想要做一些自...
    shenzhenboy閱讀 1,633評論 2 8
  • 版本記錄 前言 OpenGL ES是一個強大的圖形庫,是跨平臺的圖形API挥唠,屬于OpenGL的一個簡化版本抵恋。iOS...
    刀客傳奇閱讀 7,545評論 0 2
  • --繪圖與濾鏡全面解析 概述 在iOS中可以很容易的開發(fā)出絢麗的界面效果,一方面得益于成功系統(tǒng)的設計宝磨,另一方面得益...
    韓七夏閱讀 2,717評論 2 10
  • 童年不可追弧关, 沒有人能回到無憂無慮的孩提。 但在喧囂浮躁的世界中唤锉, 保持一顆純粹透亮的童心世囊, 生活也就多了幾分趣味...
    清晨那抹綠陽閱讀 137評論 0 0
  • 今天是周末的緣故么?賞花的人像潮水一樣窿祥,整個櫻花園株憾,到處都是陶醉著的人呢。 踩著滿地落英晒衩,心頭滋生出莫名的疼惜嗤瞎,微...
    秋之語閱讀 383評論 14 7