? ? ?這篇文章主要是在開發(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ù)熬北。