AVCapture之2——YUV轉(zhuǎn)RGB

上次我用AVFoundation的提供的AVCaptureVideoPreviewLayer,把攝像頭捕獲的數(shù)據(jù)顯示出來械荷。但是一般情況下涌矢,數(shù)據(jù)有可能是網(wǎng)絡(luò)或者文件中的,這時(shí)AVCaptureVideoPreviewLayer就沒法用了包券。

先普及一點(diǎn)視頻編碼的知識(shí)。視頻就像電影炫贤,由一張張圖片連續(xù)起來溅固。但是我們存儲(chǔ)的時(shí)候也這樣,就太浪費(fèi)空間了兰珍。一般取一張關(guān)鍵的圖片(I幀)侍郭,下一張圖片只保存上一圖片不同部分(P幀)。等到下一個(gè)畫面出現(xiàn)掠河,再重復(fù)上面的動(dòng)作亮元。所以最后保存的是 IPPPPPIPPPPP這種形式,每一段重復(fù)稱之為GOP唠摹。有的編碼方式還有B幀爆捞,即前向參考幀。視頻編碼很復(fù)雜勾拉,推薦這篇文章 http://www.skywind.me/blog/archives/1609 (需翻墻)煮甥。

解碼播放的流程就相對(duì)簡(jiǎn)單:

  1. 收到一段GOP數(shù)據(jù)盗温,丟給解碼器
  2. 解碼器還原出一幀一幀的圖像,丟給前端
  3. 前端把圖像繪制出來

需要特別指出的是成肘,視頻中的圖像是YUV編碼格式肌访,類似于RGB。為什么要選YUV艇劫,因?yàn)楸容^省空間。

上一篇文章提到惩激,AVCaptureSession能夠指定不同的輸出端店煞,可以是文件,可以是AVCaptureVideoPreviewLayer风钻,當(dāng)然也可以是原始數(shù)據(jù)AVCaptureVideoDataOutput顷蟀。

    //-- Create the output for the capture session.
    AVCaptureVideoDataOutput * dataOutput = [[AVCaptureVideoDataOutput alloc] init];
    [dataOutput setAlwaysDiscardsLateVideoFrames:YES]; // Probably want to set this to NO when recording
    
    //-- Set to YUV420.
    [dataOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange]
                                                             forKey:(id)kCVPixelBufferPixelFormatTypeKey]]; // Necessary for manual preview
    
    // Set dispatch to be on the main thread so OpenGL can do things with the data
    [dataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
    
    [_captureSession addOutput:dataOutput];

dataOutput需要指定kCVPixelBufferPixelFormatTypeKey,這里用的YUV格式骡技,h264解碼出來的也是這種格式鸣个。

設(shè)置好后,攝像頭捕捉的數(shù)據(jù)就回調(diào)到這個(gè)方法

- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection

sampleBuffer就是我們需要的數(shù)據(jù)對(duì)象布朦。

CMSampleBuffer

我們這里是Uncompressed Image囤萤,而Image就保存在CVPixelBuffer中。

渲染方式有兩種:

  1. CVPixelBuffer轉(zhuǎn)換為CGImage是趴,交給NSImageView或UIImageView
  2. 交給OpenGL渲染

這次選方法1

/*
 
  http://stackoverflow.com/questions/8838481/kcvpixelformattype-420ypcbcr8biplanarfullrange-frame-to-uiimage-conversion
*/
#define clamp(a) (a>255?255:(a<0?0:a))

- (NSImage *)imageFromSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    CVPixelBufferLockBaseAddress(imageBuffer,0);
    
    size_t width = CVPixelBufferGetWidth(imageBuffer);
    size_t height = CVPixelBufferGetHeight(imageBuffer);
    uint8_t *yBuffer = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
    size_t yPitch = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
    uint8_t *cbCrBuffer = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1);
    size_t cbCrPitch = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1);
    
    int bytesPerPixel = 4;
    uint8_t *rgbBuffer = malloc(width * height * bytesPerPixel);
    
    for(int y = 0; y < height; y++) {
        uint8_t *rgbBufferLine = &rgbBuffer[y * width * bytesPerPixel];
        uint8_t *yBufferLine = &yBuffer[y * yPitch];
        uint8_t *cbCrBufferLine = &cbCrBuffer[(y >> 1) * cbCrPitch];
        
        for(int x = 0; x < width; x++) {
            int16_t y = yBufferLine[x];
            int16_t cb = cbCrBufferLine[x & ~1] - 128;
            int16_t cr = cbCrBufferLine[x | 1] - 128;
            
            uint8_t *rgbOutput = &rgbBufferLine[x*bytesPerPixel];
            
            int16_t r = (int16_t)roundf( y + cr *  1.4 );
            int16_t g = (int16_t)roundf( y + cb * -0.343 + cr * -0.711 );
            int16_t b = (int16_t)roundf( y + cb *  1.765);
            
            rgbOutput[0] = 0xff;
            rgbOutput[1] = clamp(b);
            rgbOutput[2] = clamp(g);
            rgbOutput[3] = clamp(r);
        }
    }
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(rgbBuffer, width, height, 8, width * bytesPerPixel, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);
    CGImageRef quartzImage = CGBitmapContextCreateImage(context);
//    UIImage *image = [UIImage imageWithCGImage:quartzImage];
    NSImage *image = [[NSImage alloc] initWithCGImage:quartzImage size:NSZeroSize];
    
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
    CGImageRelease(quartzImage);
    free(rgbBuffer);
    
    CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
    
    return image;
}


- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection
{
    NSImage *nsImage = [self imageFromSampleBuffer:sampleBuffer];
    [self.cameraView performSelectorOnMainThread:@selector(setImage:) withObject:nsImage waitUntilDone:YES];
}

轉(zhuǎn)換過程比較繁瑣涛舍,基本原理就是取出YUV的數(shù)據(jù),通過一些數(shù)學(xué)變換變成RGB唆途,然后再在內(nèi)存中繪制富雅。

工程代碼:https://github.com/annidy/AVCapturePreview2

UIImageView刷新圖片是非常耗CPU,整個(gè)流程跑下來CPU占用率60%多肛搬,而fsp也只有13左右没佑,其中50%都是setImage消耗,而用AVCaptureVideoPreviewLayer總CPU不到5%温赔。

cpu占用率

結(jié)論:通過ImageView渲染方式理論上可行蛤奢,但是有比較大的性能問題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末让腹,一起剝皮案震驚了整個(gè)濱河市远剩,隨后出現(xiàn)的幾起案子骇窍,更是在濱河造成了極大的恐慌瓜晤,老刑警劉巖腹纳,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驱犹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡足画,警方通過查閱死者的電腦和手機(jī)雄驹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門淹辞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人象缀,你說我怎么就攤上這事蔬将。” “怎么了央星?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵霞怀,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我毙石,道長(zhǎng)颓遏,這世上最難降的妖魔是什么徐矩? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任丧蘸,我火速辦了婚禮,結(jié)果婚禮上力喷,老公的妹妹穿的比我還像新娘。我一直安慰自己弟孟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布拂募。 她就那樣靜靜地躺著窟她,像睡著了一般。 火紅的嫁衣襯著肌膚如雪震糖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天论咏,我揣著相機(jī)與錄音优炬,去河邊找鬼厅贪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛养涮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贬芥,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼宣决,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼昏苏!你這毒婦竟也來了尊沸?” 一聲冷哼從身側(cè)響起贤惯,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎屁商,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜡镶,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡恤筛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了望伦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡屯伞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出豪直,到底是詐尸還是另有隱情,我是刑警寧澤顶伞,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布剑梳,位于F島的核電站滑潘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏语卤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一钮孵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧巴席,春花似錦诅需、人聲如沸漾唉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽般此。三九已至,卻和暖如春铐懊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背居扒。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工丑慎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人竿裂。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像进副,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子影斑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

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