iOS-WebRTC怎么獲取某幀圖像

? ? ?這篇文章主要是在開發(fā)中遇到了解恰,需要在視頻通話時锋八,取出某幀圖像,針對這幀圖做后續(xù)操作的求护盈,話不多說挟纱,直接畫重點。

相信搜這個問題的兄弟基本已經(jīng)開發(fā)了一段時間的WebRTC腐宋,所以下面不做基礎(chǔ)的介紹紊服,直接告訴你位置。

使用的webrtc版本為2018年3月的某個版本胸竞,具體記不清了欺嗤。當我們創(chuàng)建本地視頻流的時候都會用到如下代碼



#pragma mark -- 創(chuàng)建本地視頻流

- (RTCVideoTrack *)createLocalVideoTrack:(CameraPosition)cameraPosition { RTCMediaConstraints *cameraConstraints = [[RTCMediaConstraints alloc] initWithMandatoryConstraints:[self currentMediaConstraint] optionalConstraints: nil];

RTCAVFoundationVideoSource *source = [self.factory avFoundationVideoSourceWithConstraints:cameraConstraints];

RTCVideoTrack *localVideoTrack = [self.factory videoTrackWithSource:source trackId:kARDVideoTrackId];

//這里是重點,找到你項目中創(chuàng)建的本地RTCVideoTrack(這個在開發(fā)中一定是會用到的卫枝,細心找下就好煎饼,創(chuàng)建方法應(yīng)該都和我寫的這個函數(shù)類似),然后添加代理到本地剃盾,有興趣的朋友可以點進RTCVideoTrack類里看下方法腺占,在某個類添加代理后淤袜,并實現(xiàn)代理方法痒谴,便可以在這個類里回調(diào)回來視頻的每一幀圖片了

[localVideoTrack addRenderer:self];

localView上 self.localView.captureSession = source.captureSession;

return localVideoTrack;

}


記得在加代理的類里加上遵循協(xié)議,上面代碼里[localVideoTrack addRenderer:self];這里面的self就是JanusServiceManager這個類

@interface JanusServiceManager()<RTCVideoRenderer>

再然后在你這個類里實現(xiàn)代理方法

#pragma mark -- RTCVideoRenderer---------------添加這個代理獲取webrtc內(nèi)部buffer轉(zhuǎn)換image

- (UIImage *)renderFrame:(RTCVideoFrame *)frame {

//代理方法只能獲取到RTCVideoFrame铡羡,感興趣的可以在你項目里點進去看下里面的構(gòu)成积蔚,我們下面做的就是從RTCVideoFrame里特有的buffer內(nèi)轉(zhuǎn)換成我們傳統(tǒng)用到的UIImage,當然這個buffer還可以轉(zhuǎn)成其他的格式供開發(fā)者使用烦周,這個就看個人需求了尽爆,我這里只做一個普遍需求的轉(zhuǎn)換

UIImage *image = [janusFactory convertRTVFrameToUIImage:frame rotation:frame.rotation isBackCamera:self.AVFoundationVideoSource.useBackCamera];

return image;

}

//下面這個是代理的另外一個方法,放在這就行读慎,不用做任何操作漱贱,因為是必須實現(xiàn)的,不寫會崩潰

- (void)setSize:(CGSize)size {

}


接下來是轉(zhuǎn)換

傳入RTCVideoFrame 后面的rotation是webrtc內(nèi)部的一個枚舉值夭委,因為大家都知道攝像頭取出來的圖片經(jīng)常由于你的橫豎屏不同幅狮,取到的原始圖片的rotation的都是不一樣的,webrtc這里都會給出原始圖片的旋轉(zhuǎn)角度,rotation是為轉(zhuǎn)換為UIImage成功之后崇摄,要把UIImage在旋轉(zhuǎn)成正確的方向擎值,這里的旋轉(zhuǎn)方向可能見仁見智了,總之逐抑,只要你的UIImage能轉(zhuǎn)成功鸠儿,再去變化旋轉(zhuǎn)都不是問題。isBackCamera是webrtc返回的用的是前攝像頭還是后攝像頭的參數(shù)厕氨,這個參數(shù)會影響最后圖片的左右进每,后攝像頭沒有問題,前攝像頭會發(fā)現(xiàn)圖片被左右鏡像轉(zhuǎn)換了命斧,需要再轉(zhuǎn)回來品追。

//這里有個點需要說明下,webrtc在ios上的視頻流的數(shù)據(jù)是YUVi420的冯丙,所以我們需要對應(yīng)的方法把YUVI420的數(shù)據(jù)轉(zhuǎn)換回RGB然后轉(zhuǎn)成Image肉瓦。如果你下載過webrtc的ios端的源碼,你就會知道胃惜,其實它內(nèi)部取視頻流的時候跟我們平常使用相機取視頻流時使用的方法都一樣泞莉,文章最后會解釋下。

+ (UIImage *)convertRTVFrameToUIImage:(RTCVideoFrame *)frame rotation:(int)rotation isBackCamera:(BOOL)isBackCamera{

? ? RTCCVPixelBuffer *bufferrr = frame.buffer;


? ? CVImageBufferRef imageBuffer = bufferrr.pixelBuffer;

? ? CVPixelBufferLockBaseAddress(imageBuffer,0);


? ? size_t width = CVPixelBufferGetWidth(imageBuffer);

? ? size_t height = CVPixelBufferGetHeight(imageBuffer);

? ? uint8_t *yBuffer = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);

? ? size_t yPitch = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);

? ? uint8_t *cbCrBuffer = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1);

? ? size_t cbCrPitch = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1);


? ? int bytesPerPixel = 4;

? ? uint8_t *rgbBuffer = (uint8_t *)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];

//到這里 image其實已經(jīng)轉(zhuǎn)換成功了船殉,這個時候你得到的image已經(jīng)可以顯示出來了鲫趁,但是這張image可能在方向上還有問題,我下面根據(jù)自己遇到的情況做了轉(zhuǎn)換利虫,這里就比較靈活了挨厚,大家根據(jù)自己遇到的實際情況,進行調(diào)整即可

? ? CGContextRelease(context);

? ? CGColorSpaceRelease(colorSpace);

? ? CGImageRelease(quartzImage);

? ? free(rgbBuffer);


? ? CVPixelBufferUnlockBaseAddress(imageBuffer, 0);


? ? UIImageOrientation imageOrientation;

? ? if (rotation == 90) {


? ? ? ? imageOrientation = isBackCamera ?UIImageOrientationRight:UIImageOrientationLeftMirrored;

? ? }

//下面這個方法糠惫,是對圖片進行了方向矯正疫剃,后面會貼出實際實現(xiàn)代碼,拿去直接用就好

? ? UIImage *resultImage = [janusFactory fixOrientation:image rotation:imageOrientation];

? ? return resultImage;

}

//下面是矯正圖片方向的代碼硼讽,直接用就好巢价,可以根據(jù)實際情況多測試下,看看不同orientation會輸出的實際效果

+ (UIImage *)fixOrientation:(UIImage *)image rotation:(UIImageOrientation)orientation

{

? ? if (orientation == UIImageOrientationUp)

? ? ? ? return self;


? ? CGAffineTransform transform = CGAffineTransformIdentity;


? ? switch (orientation) {

? ? ? ? case UIImageOrientationDown:

? ? ? ? case UIImageOrientationDownMirrored:

? ? ? ? ? ? transform = CGAffineTransformTranslate(transform, CGImageGetWidth(image.CGImage), CGImageGetHeight(image.CGImage));

? ? ? ? ? ? transform = CGAffineTransformRotate(transform, M_PI);

? ? ? ? ? ? break;


? ? ? ? case UIImageOrientationLeft:

? ? ? ? case UIImageOrientationLeftMirrored:

? ? ? ? ? ? transform = CGAffineTransformTranslate(transform, CGImageGetWidth(image.CGImage), 0);

? ? ? ? ? ? transform = CGAffineTransformRotate(transform, M_PI_2);

? ? ? ? ? ? break;


? ? ? ? case UIImageOrientationRight:

? ? ? ? case UIImageOrientationRightMirrored:

? ? ? ? ? ? transform = CGAffineTransformTranslate(transform, 0, CGImageGetHeight(image.CGImage));

? ? ? ? ? ? transform = CGAffineTransformRotate(transform, -M_PI_2);

? ? ? ? ? ? break;

? ? ? ? default:

? ? ? ? ? ? break;

? ? }


? ? switch (orientation) {

? ? ? ? case UIImageOrientationUpMirrored:

? ? ? ? case UIImageOrientationDownMirrored:

? ? ? ? ? ? transform = CGAffineTransformTranslate(transform, CGImageGetWidth(image.CGImage), 0);

? ? ? ? ? ? transform = CGAffineTransformScale(transform, -1, 1);

? ? ? ? ? ? break;


? ? ? ? case UIImageOrientationLeftMirrored:

? ? ? ? case UIImageOrientationRightMirrored:

? ? ? ? ? ? transform = CGAffineTransformTranslate(transform, CGImageGetHeight(image.CGImage), 0);

? ? ? ? ? ? transform = CGAffineTransformScale(transform, -1, 1);

? ? ? ? ? ? break;

? ? ? ? default:

? ? ? ? ? ? break;

? ? }


? ? CGContextRef ctx = CGBitmapContextCreate(NULL, CGImageGetWidth(image.CGImage), CGImageGetHeight(image.CGImage),

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? CGImageGetBitsPerComponent(image.CGImage), 0,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? CGImageGetColorSpace(image.CGImage),

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? CGImageGetBitmapInfo(image.CGImage));

? ? CGContextConcatCTM(ctx, transform);

? ? switch (orientation) {

? ? ? ? case UIImageOrientationLeft:

? ? ? ? case UIImageOrientationLeftMirrored:

? ? ? ? case UIImageOrientationRight:

? ? ? ? case UIImageOrientationRightMirrored:

? ? ? ? ? ? CGContextDrawImage(ctx, CGRectMake(0,0,CGImageGetHeight(image.CGImage),CGImageGetWidth(image.CGImage)), image.CGImage);

? ? ? ? ? ? break;

? ? ? ? default:

? ? ? ? ? ? CGContextDrawImage(ctx, CGRectMake(0,0,CGImageGetWidth(image.CGImage),CGImageGetHeight(image.CGImage)), image.CGImage);

? ? ? ? ? ? break;

? ? }

? ? CGImageRef cgimg = CGBitmapContextCreateImage(ctx);

? ? UIImage *img = [UIImage imageWithCGImage:cgimg];

? ? CGContextRelease(ctx);

? ? CGImageRelease(cgimg);

? ? return img;

}

PS:這里說下為什么要用YUVI420的對應(yīng)方法往回轉(zhuǎn)固阁,webrtc內(nèi)部也是調(diào)用了傳統(tǒng)攝像頭獲取數(shù)據(jù)的方法取數(shù)據(jù)的壤躲,這部分的實現(xiàn)在WebRTC 的 modules/video_capture/objc/rtc_video_capture_objc.mm 文件中實現(xiàn)了iOS視頻采集相關(guān)的工作,這里下載了源碼的朋友有興趣可以去看下备燃,我貼幾段代碼就可以簡單解釋這個問題


看起來很熟悉吧碉克,其實也是通過capturesession和output來獲取的數(shù)據(jù),但是可以看到webrtc內(nèi)部在設(shè)置output的時候使用的格式是kCVPixelFormatType_420YpCbCr8BiPlanarFullRange并齐,和我們平時使用的kCVPixelFormatType_32BGRA是不一樣的格式漏麦,所以上文里往回轉(zhuǎn)的方法才用YUVI420的對應(yīng)回轉(zhuǎn)方法法瑟。轉(zhuǎn)換代碼是多了些,但重點是好用唁奢。


基本上獲取幀圖像的工作都完成了霎挟,上面展示的都是核心代碼,往你自己項目里粘的時候肯定是需要稍微改動下的麻掸,如果找不到對應(yīng)地方的朋友酥夭,或者其它轉(zhuǎn)換不成功的問題,歡迎留言脊奋,看到會回復(fù)熬北。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市诚隙,隨后出現(xiàn)的幾起案子讶隐,更是在濱河造成了極大的恐慌,老刑警劉巖久又,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巫延,死亡現(xiàn)場離奇詭異,居然都是意外死亡地消,警方通過查閱死者的電腦和手機炉峰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脉执,“玉大人疼阔,你說我怎么就攤上這事“胍模” “怎么了婆廊?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長巫橄。 經(jīng)常有香客問我淘邻,道長,這世上最難降的妖魔是什么嗦随? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任列荔,我火速辦了婚禮,結(jié)果婚禮上枚尼,老公的妹妹穿的比我還像新娘。我一直安慰自己砂吞,他們只是感情好署恍,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蜻直,像睡著了一般盯质。 火紅的嫁衣襯著肌膚如雪袁串。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天呼巷,我揣著相機與錄音囱修,去河邊找鬼。 笑死王悍,一個胖子當著我的面吹牛破镰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播压储,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼鲜漩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了集惋?” 一聲冷哼從身側(cè)響起孕似,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎刮刑,沒想到半個月后喉祭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡雷绢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年臂拓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片习寸。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡胶惰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出霞溪,到底是詐尸還是另有隱情孵滞,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布鸯匹,位于F島的核電站坊饶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏殴蓬。R本人自食惡果不足惜匿级,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望染厅。 院中可真熱鬧痘绎,春花似錦、人聲如沸肖粮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涩馆。三九已至行施,卻和暖如春允坚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蛾号。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工稠项, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鲜结。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓展运,卻偏偏與公主長得像,于是被迫代替她去往敵國和親轻腺。 傳聞我的和親對象是個殘疾皇子乐疆,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351

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