自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

開源一個(gè)上架App Store的相機(jī)App

移動(dòng)開發(fā) iOS
Osho 相機(jī)是我獨(dú)立開發(fā)上架的一個(gè)相機(jī) App,它支持1:1,4:3,16:9多種分辨率拍攝,濾鏡可在取景框的實(shí)時(shí)預(yù)覽,拍攝過程可與濾鏡實(shí)時(shí)合成,支持分段拍攝,支持回刪等特性。下面先分享分享開發(fā)這個(gè) App 的一些心得體會(huì),文末會(huì)給出項(xiàng)目的下載地址,閱讀本文可能需要一點(diǎn)點(diǎn) AVFoundation 開發(fā)的基礎(chǔ)。

Osho 相機(jī)是我獨(dú)立開發(fā)上架的一個(gè)相機(jī) App,App Store地址:https://itunes.apple.com/cn/app/osho/id1203312279?mt=8。它支持1:1,4:3,16:9多種分辨率拍攝,濾鏡可在取景框的實(shí)時(shí)預(yù)覽,拍攝過程可與濾鏡實(shí)時(shí)合成,支持分段拍攝,支持回刪等特性。下面先分享分享開發(fā)這個(gè) App 的一些心得體會(huì),文末會(huì)給出項(xiàng)目的下載地址,閱讀本文可能需要一點(diǎn)點(diǎn) AVFoundation 開發(fā)的基礎(chǔ)。  

 

1、GLKView和GPUImageVideoCamera

一開始取景框的預(yù)覽我是基于 GLKView 做的,GLKView 是蘋果對 OpenGL 的封裝,我們可以使用它的回調(diào)函數(shù) -glkView:drawInRect: 進(jìn)行對處理后的 samplebuffer 渲染的工作(samplebuffer 是在相機(jī)回調(diào) didOutputSampleBuffer 產(chǎn)生的),附上當(dāng)初簡版代碼:

  1. - (CIImage *)renderImageInRect:(CGRect)rect { 
  2.  
  3.     CMSampleBufferRef sampleBuffer = _sampleBufferHolder.sampleBuffer; 
  4.  
  5.   
  6.  
  7.     if (sampleBuffer != nil) { 
  8.  
  9.         UIImage *originImage = [self imageFromSamplePlanerPixelBuffer:sampleBuffer]; 
  10.  
  11.         if (originImage) { 
  12.  
  13.            if (self.filterName && self.filterName.length > 0) { 
  14.  
  15.   
  16.  
  17.                GPUImageOutput<GPUImageInput> *filter; 
  18.  
  19.                 if ([self.filterType isEqual: @"1"]) { 
  20.  
  21.                     Class class = NSClassFromString(self.filterName); 
  22.  
  23.                     filter = [[class alloc] init]; 
  24.  
  25.                 } else { 
  26.  
  27.                     NSBundle *bundle = [NSBundle bundleForClass:self.class]; 
  28.  
  29.                     NSURL *filterAmaro = [NSURL fileURLWithPath:[bundle pathForResource:self.filterName ofType:@"acv"]]; 
  30.  
  31.                     filter = [[GPUImageToneCurveFilter alloc] initWithACVURL:filterAmaro]; 
  32.  
  33.                 } 
  34.  
  35.                 [filter forceProcessingAtSize:originImage.size]; 
  36.  
  37.                 GPUImagePicture *pic = [[GPUImagePicture alloc] initWithImage:originImage]; 
  38.  
  39.                 [pic addTarget:filter]; 
  40.  
  41.                 [filter useNextFrameForImageCapture]; 
  42.  
  43.                 [filter addTarget:self.gpuImageView]; 
  44.  
  45.                 [pic processImage];               
  46.  
  47.                 UIImage *filterImage = [filter imageFromCurrentFramebuffer]; 
  48.  
  49.                 //UIImage *filterImage = [filter imageByFilteringImage:originImage]; 
  50.  
  51.   
  52.  
  53.                 _CIImage = [[CIImage alloc] initWithCGImage:filterImage.CGImage options:nil]; 
  54.  
  55.             } else { 
  56.  
  57.             _CIImage = [CIImage imageWithCVPixelBuffer:CMSampleBufferGetImageBuffer(sampleBuffer)]; 
  58.  
  59.         } 
  60.  
  61.     }   
  62.  
  63.     CIImage *image = _CIImage; 
  64.  
  65.   
  66.  
  67.     if (image != nil) { 
  68.  
  69.         image = [image imageByApplyingTransform:self.preferredCIImageTransform]; 
  70.  
  71.   
  72.  
  73.         if (self.scaleAndResizeCIImageAutomatically) { 
  74.  
  75.            image = [self scaleAndResizeCIImage:image forRect:rect]; 
  76.  
  77.         } 
  78.  
  79.     } 
  80.  
  81.   
  82.  
  83.     return image; 
  84.  
  85.  
  86.   
  87.  
  88. - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect { 
  89.  
  90.     @autoreleasepool { 
  91.  
  92.         rect = CGRectMultiply(rect, self.contentScaleFactor); 
  93.  
  94.         glClearColor(0, 0, 0, 0); 
  95.  
  96.         glClear(GL_COLOR_BUFFER_BIT); 
  97.  
  98.   
  99.  
  100.         CIImage *image = [self renderImageInRect:rect]; 
  101.  
  102.   
  103.  
  104.         if (image != nil) { 
  105.  
  106.             [_context.CIContext drawImage:image inRect:rect fromRect:image.extent]; 
  107.  
  108.         } 
  109.  
  110.     } 
  111.  
  112.  

這樣的實(shí)現(xiàn)在低端機(jī)器上取景框會(huì)有明顯的卡頓,而且 ViewController 上的列表幾乎無法滑動(dòng),雖然手勢倒是還可以支持。 因?yàn)橐獙?shí)現(xiàn)分段拍攝與回刪等功能,采用這種方式的初衷是期望更高度的自定義,而不去使用 GPUImageVideoCamera, 畢竟我得在 AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate 這兩個(gè)回調(diào)做文章,為了滿足需求,所以得在不侵入 GPUImage 源代碼的前提下點(diǎn)功夫。

怎么樣才能在不破壞 GPUImageVideoCamera 的代碼呢?我想到兩個(gè)方法,第一個(gè)是創(chuàng)建一個(gè)類,然后把 GPUImageVideoCamera 里的代碼拷貝過來,這么做簡單粗暴,缺點(diǎn)是若以后 GPUImage 升級了,代碼維護(hù)起來是個(gè)小災(zāi)難;再來說說第二個(gè)方法——繼承,繼承是個(gè)挺優(yōu)雅的行為,可它的麻煩在于獲取不到私有變量,好在有強(qiáng)大的 runtime,解決了這個(gè)棘手的問題。下面是用 runtime 獲取私有變量:

  1. - (AVCaptureAudioDataOutput *)gpuAudioOutput { 
  2.  
  3.     Ivar var = class_getInstanceVariable([super class], "audioOutput"); 
  4.  
  5.     id nameVar = object_getIvar(self, var); 
  6.  
  7.     return nameVar; 
  8.  
  9.  

至此取景框?qū)崿F(xiàn)了濾鏡的渲染并保證了列表的滑動(dòng)幀率。

2、實(shí)時(shí)合成以及 GPUImage 的 outputImageOrientation

顧名思義,outputImageOrientation 屬性和圖像方向有關(guān)的。GPUImage 的這個(gè)屬性是對不同設(shè)備的在取景框的圖像方向做過優(yōu)化的,但這個(gè)優(yōu)化會(huì)與 videoOrientation 產(chǎn)生沖突,它會(huì)導(dǎo)致切換攝像頭導(dǎo)致圖像方向不對,也會(huì)造成拍攝完之后的視頻方向不對。 最后的解決辦法是確保攝像頭輸出的圖像方向正確,所以將其設(shè)置為 UIInterfaceOrientationPortrait,而不對 videoOrientation 進(jìn)行設(shè)置,剩下的問題就是怎樣處理拍攝完成之后視頻的方向。

先來看看視頻的實(shí)時(shí)合成,因?yàn)檫@里包含了對用戶合成的 CVPixelBufferRef 資源處理。還是使用繼承的方式繼承 GPUImageView,其中使用了 runtime 調(diào)用私有方法:

  1. SEL s = NSSelectorFromString(@"textureCoordinatesForRotation:"); 
  2.  
  3. IMP imp = [[GPUImageView class] methodForSelector:s]; 
  4.  
  5. GLfloat *(*func)(id, SEL, GPUImageRotationMode) = (void *)imp; 
  6.  
  7. GLfloat *result = [GPUImageView class] ? func([GPUImageView class], s, inputRotation) : nil; 
  8.  
  9.   
  10.  
  11. ...... 
  12.  
  13.   
  14.  
  15. glVertexAttribPointer(self.gpuDisplayTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, result);  

直奔重點(diǎn)——CVPixelBufferRef 的處理,將 renderTarget 轉(zhuǎn)換為 CGImageRef 對象,再使用 UIGraphics 獲得經(jīng) CGAffineTransform 處理過方向的 UIImage,此時(shí) UIImage 的方向并不是正常的方向,而是旋轉(zhuǎn)過90度的圖片,這么做的目的是為 videoInput 的 transform 屬性埋下伏筆。下面是 CVPixelBufferRef 的處理代碼:

  1. int width = self.gpuInputFramebufferForDisplay.size.width; 
  2.  
  3. int height = self.gpuInputFramebufferForDisplay.size.height; 
  4.  
  5.   
  6.  
  7. renderTarget = self.gpuInputFramebufferForDisplay.gpuBufferRef; 
  8.  
  9.   
  10.  
  11. NSUInteger paddedWidthOfImage = CVPixelBufferGetBytesPerRow(renderTarget) / 4.0; 
  12.  
  13. NSUInteger paddedBytesForImage = paddedWidthOfImage * (int)height * 4; 
  14.  
  15.   
  16.  
  17. glFinish(); 
  18.  
  19. CVPixelBufferLockBaseAddress(renderTarget, 0); 
  20.  
  21. GLubyte *data = (GLubyte *)CVPixelBufferGetBaseAddress(renderTarget); 
  22.  
  23. CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, paddedBytesForImage, NULL); 
  24.  
  25. CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); 
  26.  
  27. CGImageRef iref = CGImageCreate((int)width, (int)height, 8, 32, CVPixelBufferGetBytesPerRow(renderTarget), colorspace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst, ref, NULLNO, kCGRenderingIntentDefault); 
  28.  
  29.   
  30.  
  31. UIGraphicsBeginImageContext(CGSizeMake(height, width)); 
  32.  
  33. CGContextRef cgcontext = UIGraphicsGetCurrentContext(); 
  34.  
  35. CGAffineTransform transform = CGAffineTransformIdentity; 
  36.  
  37. transform = CGAffineTransformMakeTranslation(height / 2.0, width / 2.0); 
  38.  
  39. transform = CGAffineTransformRotate(transform, M_PI_2); 
  40.  
  41. transform = CGAffineTransformScale(transform, 1.0, -1.0); 
  42.  
  43. CGContextConcatCTM(cgcontext, transform); 
  44.  
  45.   
  46.  
  47. CGContextSetBlendMode(cgcontext, kCGBlendModeCopy); 
  48.  
  49. CGContextDrawImage(cgcontext, CGRectMake(0.0, 0.0, width, height), iref); 
  50.  
  51. UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 
  52.  
  53. UIGraphicsEndImageContext(); 
  54.  
  55. self.img = image; 
  56.  
  57.   
  58.  
  59. CFRelease(ref); 
  60.  
  61. CFRelease(colorspace); 
  62.  
  63. CGImageRelease(iref); 
  64.  
  65. CVPixelBufferUnlockBaseAddress(renderTarget, 0); 

 而 videoInput 的 transform 屬性設(shè)置如下:

  1. _videoInput.transform = CGAffineTransformRotate(_videoConfiguration.affineTransform, -M_PI_2); 

經(jīng)過這兩次方向的處理,合成的小視頻終于方向正常了。此處為簡版的合成視頻代碼:

  1. CIImage *image = [[CIImage alloc] initWithCGImage:img.CGImage options:nil]; 
  2.  
  3. CVPixelBufferLockBaseAddress(pixelBuffer, 0); 
  4.  
  5. [self.context.CIContext render:image toCVPixelBuffer:pixelBuffer]; 
  6.  
  7. ... 
  8.  
  9. [_videoPixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:bufferTimestamp]  

可以看到關(guān)鍵點(diǎn)還是在于上面繼承自 GPUImageView 這個(gè)類獲取到的 renderTarget 屬性,它應(yīng)該即是取景框?qū)崟r(shí)預(yù)覽的結(jié)果,我在最初的合成中是使用 sampleBuffer 轉(zhuǎn) UIImage,再通過 GPUImage 添加濾鏡,最后將 UIImage 再轉(zhuǎn) CIImage,這么做導(dǎo)致拍攝時(shí)會(huì)卡。當(dāng)時(shí)我?guī)缀跸敕艞壛?,甚至想采用拍好后再加濾鏡的方式繞過去,最后這些不純粹的方法都被我 ban 掉了。

既然濾鏡可以在取景框?qū)崟r(shí)渲染,我想到了 GPUImageView 可能有料。在閱讀過 GPUImage 的諸多源碼后,終于在 GPUImageFramebuffer.m 找到了一個(gè)叫 renderTarget 的屬性。至此,合成的功能也告一段落。

3、關(guān)于濾鏡

這里主要分享個(gè)有意思的過程。App 里有三種類型的濾鏡。基于 glsl 的、直接使用 acv 的以及直接使用 lookuptable 的。lookuptable 其實(shí)也是 photoshop 可導(dǎo)出的一種圖片,但一般的軟件都會(huì)對其加密,下面簡單提下我是如何反編譯“借用”某軟件的部分濾鏡吧。使用 Hopper Disassembler 軟件進(jìn)行反編譯,然后通過某些關(guān)鍵字的搜索,幸運(yùn)地找到了下圖的一個(gè)方法名。 

 

 

 

reverse 只能說這么多了….在開源代碼里我已將這一類敏感的濾鏡剔除了。

小結(jié)

開發(fā)相機(jī) App 是個(gè)挺有意思的過程,在其中邂逅不少優(yōu)秀開源代碼,向開源代碼學(xué)習(xí),才能避免自己總是寫出一成不變的代碼。最后附上項(xiàng)目的開源地址 https://github.com/hawk0620/ZPCamera,希望能夠幫到有需要的朋友,也歡迎 star 和 pull request。 

責(zé)任編輯:龐桂玉 來源: iOS大全
相關(guān)推薦

2019-07-04 14:11:48

App StoreiOS應(yīng)用開發(fā)

2022-01-17 09:22:42

SwiftUI App Store開源

2020-02-22 13:00:26

App StoreiOSOffice

2012-04-25 22:41:37

APP

2012-06-15 09:43:20

蘋果App Store盜版書

2011-12-28 10:09:53

云計(jì)算App StoreCA

2013-08-05 14:52:01

蘋果微信5.0App Store

2013-08-06 14:37:37

App Store定價(jià)移動(dòng)應(yīng)用定價(jià)移動(dòng)應(yīng)用市場

2011-12-31 21:19:56

App Store

2013-01-21 10:27:47

蘋果App Store下架

2017-08-14 10:05:50

開發(fā)者App Store

2013-07-30 16:27:20

App Store潛規(guī)則

2021-06-22 16:21:40

鴻蒙HarmonyOS應(yīng)用

2012-06-15 09:35:17

2010-08-26 11:00:23

2012-04-26 13:30:05

iPhoneApp Store發(fā)布程序

2011-10-11 08:39:04

LinuxApp store

2012-03-07 10:50:39

APP經(jīng)驗(yàn)

2014-07-16 09:56:52

2023-05-19 19:35:11

ChatGPT數(shù)據(jù)泄露
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號