├── README.md ├── XDXCropSampleBuffer.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── demon.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── demon.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── XDXCropSampleBuffer.xcscheme │ └── xcschememanagement.plist ├── XDXCropSampleBuffer ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.h ├── ViewController.m ├── XDXCropView.h ├── XDXCropView.m └── main.m ├── XDXCropSampleBufferTests ├── Info.plist └── XDXCropSampleBufferTests.m └── XDXCropSampleBufferUITests ├── Info.plist └── XDXCropSampleBufferUITests.m /README.md: -------------------------------------------------------------------------------- 1 | ## iOS开发中截取相机部分画面,切割sampleBuffer(Crop sample buffer) 2 | 3 | 4 | ### 本例需求:在类似直播的功能界面,二维码扫描,人脸识别或其他需求中的功能界面或其他需求中需要从相机捕获的画面中单独截取出一部分区域。 5 | ### 原理:由于需要截取相机捕获整个画面其中一部分,所以也就必须拿到那一部分画面的数据,又因为相机AVCaptureVideoDataOutputSampleBufferDelegate中的sampleBuffer为系统私有的数据结构不可直接操作,所以需要将其转换成可以切割的数据结构再进行切割,网上有种思路说将sampleBuffer间接转换为UIImage再对图片切割,这种思路繁琐且性能低,本例将sampleBuffer转换为CoreImage中的CIImage,性能相对较高且降低代码繁琐度。 6 | 7 | ### 最终效果如下, 绿色框中即为截图的画面,长按可以移动。 8 | ![绿色框为截取部分](http://upload-images.jianshu.io/upload_images/5086522-8dcd2cde5f0b23a3.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 9 | 10 | #### GitHub地址(附代码) : [Crop sample buffer](https://github.com/ChengyangLi/Crop-sample-buffer) 11 | #### 简书地址 : [Crop sample buffer](http://www.jianshu.com/p/ac79a80f1af2) 12 | #### 博客地址 : [Crop sample buffer](https://chengyangli.github.io/2017/07/12/cropSampleBuffer/) 13 | #### 掘金地址 : [Crop sample buffer](https://juejin.im/post/5a8fe2a66fb9a063317c6828) 14 | 15 | ## 注意:使用ARC与MRC下代码有所区别,已经在项目中标注好,主要为管理全局的CIContext对象,它在初始化的方法中编译器没有对其进行retain,所以,调用会报错。 16 | ![cicontextError](http://upload-images.jianshu.io/upload_images/5086522-5f510e448af32d4d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 17 | 18 | ### 使用场景 19 | - 本项目中相机捕捉的背景分辨率默认设置为2K(即1920*1080),可切换为4K ,所以需要iPhone 6s以上的设备才支持。 20 | - 本例可以使用CPU/GPU切割,在VC中需要在cropView初始化前设置isOpenGPU的值,打开则使用GPU,否则CPU 21 | - 本例只实现了横屏下的Crop功能,本例默认始终为横屏状态,未做竖屏处理。 22 | 23 | ## 基本配置 24 | 1.配置相机基本环境(初始化AVCaptureSession,设置代理,开启),在示例代码中有,这里不再重复。 25 | 26 | 2.通过AVCaptureVideoDataOutputSampleBufferDelegate代理中拿到原始画面数据(CMSampleBufferRef)进行处理 27 | 28 | ## 实现途径 29 | ### 1.利用CPU软件截取(CPU进行计算并切割,消耗性能较大) 30 | - (CMSampleBufferRef)cropSampleBufferBySoftware:(CMSampleBufferRef)sampleBuffer; 31 | 32 | 33 | ### 2.利用 硬件截取(利用Apple官方公开的方法利用硬件进行切割,性能较好, 但仍有问题待解决) 34 | - (CMSampleBufferRef)cropSampleBufferByHardware:(CMSampleBufferRef)buffer; 35 | 36 | ### 解析 37 | ``` 38 | // Called whenever an AVCaptureVideoDataOutput instance outputs a new video frame. 每产生一帧视频帧时调用一次 39 | - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { 40 | CMSampleBufferRef cropSampleBuffer; 41 | 42 | #warning 两种切割方式任选其一,GPU切割性能较好,CPU切割取决于设备,一般时间长会掉帧。 43 | if (self.isOpenGPU) { 44 | cropSampleBuffer = [self.cropView cropSampleBufferByHardware:sampleBuffer]; 45 | }else { 46 | cropSampleBuffer = [self.cropView cropSampleBufferBySoftware:sampleBuffer]; 47 | } 48 | 49 | // 使用完后必须显式release,不在iOS自动回收范围 50 | CFRelease(cropSampleBuffer); 51 | } 52 | 53 | ``` 54 | - 以上方法为每产生一帧视频帧时调用一次的相机代理,其中sampleBuffer为每帧画面的原始数据,需要对原始数据进行切割处理方可达到本例需求。注意最后一定要对cropSampleBuffer进行release避免内存溢出而发生闪退。 55 | 56 | 57 | ## 利用CPU截取 58 | 59 | ``` 60 | - (CMSampleBufferRef)cropSampleBufferBySoftware:(CMSampleBufferRef)sampleBuffer { 61 | OSStatus status; 62 | 63 | // CVPixelBufferRef pixelBuffer = [self modifyImage:buffer]; 64 | CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 65 | // Lock the image buffer 66 | CVPixelBufferLockBaseAddress(imageBuffer,0); 67 | // Get information about the image 68 | uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(imageBuffer); 69 | size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); 70 | size_t width = CVPixelBufferGetWidth(imageBuffer); 71 | // size_t height = CVPixelBufferGetHeight(imageBuffer); 72 | NSInteger bytesPerPixel = bytesPerRow/width; 73 | 74 | // YUV 420 Rule 75 | if (_cropX % 2 != 0) _cropX += 1; 76 | NSInteger baseAddressStart = _cropY*bytesPerRow+bytesPerPixel*_cropX; 77 | static NSInteger lastAddressStart = 0; 78 | lastAddressStart = baseAddressStart; 79 | 80 | // pixbuffer 与 videoInfo 只有位置变换或者切换分辨率或者相机重启时需要更新,其余情况不需要,Demo里只写了位置更新,其余情况自行添加 81 | // NSLog(@"demon pix first : %zu - %zu - %@ - %d - %d - %d -%d",width, height, self.currentResolution,_cropX,_cropY,self.currentResolutionW,self.currentResolutionH); 82 | static CVPixelBufferRef pixbuffer = NULL; 83 | static CMVideoFormatDescriptionRef videoInfo = NULL; 84 | 85 | // x,y changed need to reset pixbuffer and videoinfo 86 | if (lastAddressStart != baseAddressStart) { 87 | if (pixbuffer != NULL) { 88 | CVPixelBufferRelease(pixbuffer); 89 | pixbuffer = NULL; 90 | } 91 | 92 | if (videoInfo != NULL) { 93 | CFRelease(videoInfo); 94 | videoInfo = NULL; 95 | } 96 | } 97 | 98 | if (pixbuffer == NULL) { 99 | NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: 100 | [NSNumber numberWithBool : YES], kCVPixelBufferCGImageCompatibilityKey, 101 | [NSNumber numberWithBool : YES], kCVPixelBufferCGBitmapContextCompatibilityKey, 102 | [NSNumber numberWithInt : g_width_size], kCVPixelBufferWidthKey, 103 | [NSNumber numberWithInt : g_height_size], kCVPixelBufferHeightKey, 104 | nil]; 105 | 106 | status = CVPixelBufferCreateWithBytes(kCFAllocatorDefault, g_width_size, g_height_size, kCVPixelFormatType_32BGRA, &baseAddress[baseAddressStart], bytesPerRow, NULL, NULL, (__bridge CFDictionaryRef)options, &pixbuffer); 107 | if (status != 0) { 108 | NSLog(@"Crop CVPixelBufferCreateWithBytes error %d",(int)status); 109 | return NULL; 110 | } 111 | } 112 | 113 | CVPixelBufferUnlockBaseAddress(imageBuffer,0); 114 | 115 | CMSampleTimingInfo sampleTime = { 116 | .duration = CMSampleBufferGetDuration(sampleBuffer), 117 | .presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer), 118 | .decodeTimeStamp = CMSampleBufferGetDecodeTimeStamp(sampleBuffer) 119 | }; 120 | 121 | if (videoInfo == NULL) { 122 | status = CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixbuffer, &videoInfo); 123 | if (status != 0) NSLog(@"Crop CMVideoFormatDescriptionCreateForImageBuffer error %d",(int)status); 124 | } 125 | 126 | CMSampleBufferRef cropBuffer = NULL; 127 | status = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, pixbuffer, true, NULL, NULL, videoInfo, &sampleTime, &cropBuffer); 128 | if (status != 0) NSLog(@"Crop CMSampleBufferCreateForImageBuffer error %d",(int)status); 129 | 130 | lastAddressStart = baseAddressStart; 131 | 132 | return cropBuffer; 133 | } 134 | 135 | ``` 136 | - 以上方法为切割sampleBuffer的对象方法 137 | 首先从CMSampleBufferRef中提取出CVImageBufferRef数据结构,然后对CVImageBufferRef进行加锁处理,如果要进行页面渲染,需要一个和OpenGL缓冲兼容的图像。用相机API创建的图像已经兼容,您可以马上映射他们进行输入。假设你从已有画面中截取一个新的画面,用作其他处理,你必须创建一种特殊的属性用来创建图像。对于图像的属性必须有Crop宽高, 作为字典的Key.因此创建字典的关键几步不可省略。 138 | 139 | > 位置的计算 140 | 141 | 在软切中,我们拿到一帧图片的数据,通过遍历其中的数据确定真正要Crop的位置,利用如下公式可求出具体位置,具体切割原理在[YUV介绍]中有提到,计算时所需的变量在以上代码中均可得到。 142 | 143 | ``` 144 | `NSInteger baseAddressStart = _cropY*bytesPerRow+bytesPerPixel*_cropX; 145 | ` 146 | ``` 147 | 148 | 注意: 149 | - 1.对X,Y坐标进行校正,因为CVPixelBufferCreateWithBytes是按照像素进行切割,所以需要将点转成像素,再按照比例算出当前位置。即为上述代码的int cropX = (int)(currentResolutionW / kScreenWidth * self.cropView.frame.origin.x); currentResolutionW为当前分辨率的宽度,kScreenWidth为屏幕实际宽度。 150 | - 2.根据YUV 420的规则,每4个Y共用1个UV,而一行有2个Y,所以取点必须按照偶数取点。利用CPU切割中使用的方法为YUV分隔法,具体切割方式请参考[YUV介绍](http://www.jianshu.com/p/a91502c00fb0) 151 | - 3.本例中声明pixelBuffer与videoInfo均为静态变量,为了节省每次创建浪费内存,但是有三种情况需要重置它们:位置变化,分辨率改变,重启相机。文章最后注意详细提到。 152 | 153 | ``` 154 | // hardware crop 155 | - (CMSampleBufferRef)cropSampleBufferByHardware:(CMSampleBufferRef)buffer { 156 | // a CMSampleBuffer's CVImageBuffer of media data. 157 | CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(buffer); 158 | CGRect cropRect = CGRectMake(_cropX, _cropY, g_width_size, g_height_size); 159 | // log4cplus_debug("Crop", "dropRect x: %f - y : %f - width : %zu - height : %zu", cropViewX, cropViewY, width, height); 160 | 161 | /* 162 | First, to render to a texture, you need an image that is compatible with the OpenGL texture cache. Images that were created with the camera API are already compatible and you can immediately map them for inputs. Suppose you want to create an image to render on and later read out for some other processing though. You have to have create the image with a special property. The attributes for the image must have kCVPixelBufferIOSurfacePropertiesKey as one of the keys to the dictionary. 163 | 如果要进行页面渲染,需要一个和OpenGL缓冲兼容的图像。用相机API创建的图像已经兼容,您可以马上映射他们进行输入。假设你从已有画面中截取一个新的画面,用作其他处理,你必须创建一种特殊的属性用来创建图像。对于图像的属性必须有kCVPixelBufferIOSurfacePropertiesKey 作为字典的Key.因此以下步骤不可省略 164 | */ 165 | 166 | OSStatus status; 167 | 168 | /* Only resolution has changed we need to reset pixBuffer and videoInfo so that reduce calculate count */ 169 | static CVPixelBufferRef pixbuffer = NULL; 170 | static CMVideoFormatDescriptionRef videoInfo = NULL; 171 | 172 | if (pixbuffer == NULL) { 173 | NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: 174 | [NSNumber numberWithInt:g_width_size], kCVPixelBufferWidthKey, 175 | [NSNumber numberWithInt:g_height_size], kCVPixelBufferHeightKey, nil]; 176 | status = CVPixelBufferCreate(kCFAllocatorSystemDefault, g_width_size, g_height_size, kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, (__bridge CFDictionaryRef)options, &pixbuffer); 177 | // ensures that the CVPixelBuffer is accessible in system memory. This should only be called if the base address is going to be used and the pixel data will be accessed by the CPU 178 | if (status != noErr) { 179 | NSLog(@"Crop CVPixelBufferCreate error %d",(int)status); 180 | return NULL; 181 | } 182 | } 183 | 184 | CIImage *ciImage = [CIImage imageWithCVImageBuffer:imageBuffer]; 185 | ciImage = [ciImage imageByCroppingToRect:cropRect]; 186 | // Ciimage get real image is not in the original point after excute crop. So we need to pan. 187 | ciImage = [ciImage imageByApplyingTransform:CGAffineTransformMakeTranslation(-_cropX, -_cropY)]; 188 | 189 | static CIContext *ciContext = nil; 190 | if (ciContext == nil) { 191 | // NSMutableDictionary *options = [[NSMutableDictionary alloc] init]; 192 | // [options setObject:[NSNull null] forKey:kCIContextWorkingColorSpace]; 193 | // [options setObject:@0 forKey:kCIContextUseSoftwareRenderer]; 194 | EAGLContext *eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; 195 | ciContext = [CIContext contextWithEAGLContext:eaglContext options:nil]; 196 | } 197 | [ciContext render:ciImage toCVPixelBuffer:pixbuffer]; 198 | // [ciContext render:ciImage toCVPixelBuffer:pixbuffer bounds:cropRect colorSpace:nil]; 199 | 200 | CMSampleTimingInfo sampleTime = { 201 | .duration = CMSampleBufferGetDuration(buffer), 202 | .presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(buffer), 203 | .decodeTimeStamp = CMSampleBufferGetDecodeTimeStamp(buffer) 204 | }; 205 | 206 | if (videoInfo == NULL) { 207 | status = CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixbuffer, &videoInfo); 208 | if (status != 0) NSLog(@"Crop CMVideoFormatDescriptionCreateForImageBuffer error %d",(int)status); 209 | } 210 | 211 | CMSampleBufferRef cropBuffer; 212 | status = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, pixbuffer, true, NULL, NULL, videoInfo, &sampleTime, &cropBuffer); 213 | if (status != 0) NSLog(@"Crop CMSampleBufferCreateForImageBuffer error %d",(int)status); 214 | 215 | return cropBuffer; 216 | } 217 | 218 | ``` 219 | - 以上为硬件切割的方法,硬件切割利用GPU进行切割,主要利用CoreImage中CIContext 对象进行渲染。 220 | 221 | - CoreImage and UIKit coordinates (CoreImage 与 UIKit坐标系问题):我在开始做的时候跟正常一样用设定的位置对图像进行切割,但是发现,切出来的位置不对,通过上网查阅发现一个有趣的现象CoreImage 与 UIKit坐标系不相同 222 | 如下图: 223 | 正常UIKit坐标系是以左上角为原点: 224 | 225 | ![](https://cl.ly/0O070Z391Y3m/Snip20170802_2.png) 226 | 227 | 而CoreImage坐标系是以左下角为原点:(在CoreImage中,每个图像的坐标系是独立于设备的) 228 | 229 | ![](https://cl.ly/1B3X230y0l1m/Snip20170802_1.png) 230 | 231 | 所以切割的时候一定要注意转换Y,X的位置是正确的,Y是相反的。 232 | 233 | - 如果要进行页面渲染,需要一个和OpenGL缓冲兼容的图像。用相机API创建的图像已经兼容,您可以马上映射他们进行输入。假设你从已有画面中截取一个新的画面,用作其他处理,你必须创建一种特殊的属性用来创建图像。对于图像的属性必须有宽高 作为字典的Key.因此创建字典的关键几步不可省略。 234 | - 对CoreImage进行切割有两种切割的方法均可用: 235 | 1. ```ciImage = [ciImage imageByCroppingToRect:cropRect];``` 如果使用此行代码则渲染时用``` [ciContext render:ciImage toCVPixelBuffer:pixelBuffer]; ``` 236 | 2. 或者直接使用: ``` [ciContext render:ciImage toCVPixelBuffer:pixelBuffer bounds:cropRect colorSpace:nil]; ``` 237 | - 注意:CIContext 中包含图像大量上下文信息,不能在回调中多次调用,官方建议只初始化一次。但是注意ARC,MRC区别。 238 | 239 | ### 注意: 240 | ##### 1. 使用ARC与MRC下代码有所区别,已经在项目中标注好,主要为管理全局的CIContext对象,它在初始化的方法中编译器没有对其进行retain,所以,调用会报错。 241 | ![cicontextError](http://upload-images.jianshu.io/upload_images/5086522-5f510e448af32d4d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 242 | 243 | ##### 2.切换前后置摄像头:因为不同机型的前后置摄像头差别较大,一种处理手段是在记录iphone机型crop的plist文件中增加前后置摄像头支持分辨率的属性,然后在代码中根据plist映射出来的模型进行分别引用。另一种方案是做自动降级处理,例如后置支持2K,前置支持720P,则转换后检测到前置不支持2K就自动将前置降低一个等级,直到找到需要的等级。如果这样操作处理逻辑较多且初看不易理解,而前置切割功能适用范围不大,所以暂时只支持后置切割。 244 | 245 | ### 补充说明 246 | - 屏幕逻辑分辨率与视频分辨率 247 | 248 | 1. Point and pixel的区别 249 | 因为此类说明网上很多,这里就不做太多具体阐述,仅仅简述一下 250 | Point 即是设备的逻辑分辨率,即[UIScreen mainScreen].bounds.size.width 得到的设备的宽高,所以点可以简单理解为iOS开发中的坐标系,方便对界面元素进行描述。 251 | 252 | 2. Pixel: 像素则是比点更精确的单位,在普通屏中1点=1像素,Retina屏中1点=2像素。 253 | 254 | 3. 分辨率 分辨率需要根据不同机型所支持的最大分辨率进行设置,例如iPhone 6S以上机型支持4k(3840 * 2160)分辨率拍摄视频。而当我们进行Crop操作的时候调用的API正是通过像素来进行切割,所以我们操作的单位是pixel而不是point.下面会有详细介绍。 255 | 256 | - ARC, MRC下所做工作不同 257 | 258 | > CIContext 的初始化 259 | 260 | 首先应该将CIContext声明为全局变量或静态变量,因为CIContext初始化一次内部含有大量信息,比较耗内存,且只是渲染的时候使用,无需每次都初始化,然后如下如果在MRC中初始化完成后并未对ciContext发出retain的消息,所以需要手动retain,但在ARC下系统会自动完成此操作。 261 | 262 | ``` 263 | ARC: 264 | 265 | static CIContext *ciContext = NULL; 266 | ciContext = [CIContext contextWithOptions:nil]; 267 | ``` 268 | 269 | ``` 270 | MRC: 271 | 272 | static CIContext *ciContext = NULL; 273 | ciContext = [CIContext contextWithOptions:nil]; 274 | [ciContext retain]; 275 | ``` 276 | 277 | - 坐标问题 278 | 279 | #####1. 理解点与像素的对应关系 280 | 首先CropView需要在手机显示出来,所以坐标系还是UIKit的坐标系,左上角为原点,宽高分别为不同手机的宽高(如iPhone8 : 375*667, iPhone8P : 414 * 736, iPhoneX : 375 * 816),但是我们需要算出实际分辨率下CropView的坐标,即我们可以把当前获取的cropView的x,y点的位置转换成对应pixel的位置。 281 | 282 | ``` 283 | // 注意这里求的是X的像素坐标,以iPhone 8 为例 (点为375 * 667),分辨率为(1920 * 1080) 284 | _cropX = (int)(_currentResolutionW / _screenWidth * (cropView.frame.origin.x); 285 | 即 286 | _cropX = (int)(1920 / 375 * 当前cropView的x点坐标; 287 | ``` 288 | 289 | #####2. CPU / GPU 两种方式切割时坐标系的位置不同 290 | > 原点位置 291 | 292 | CPU : UIKit为坐标系,原点在左上角 293 | 294 | GPU : CoreImage为坐标系,原点在左下角 295 | 296 | ##### 因此计算时如果使用GPU, y的坐标是相反的,我们需要通过如下公式转换,即将点对应转为正常以左上角为原点坐标系中的点。 297 | 298 | ``` 299 | _cropY = (int)(_currentResolutionH / _screenHeight * (_screenHeight - self.frame.origin.y - self.frame.size.height)); 300 | ``` 301 | 302 | #####3. 当手机屏幕不是16:9时,如果将视频设置为填充满屏幕则会出现偏差 303 | 304 | 需要注意的是,因为部分手机或iPad屏幕尺寸并不为16:9(iPhone X, 所有iPad (4 : 3)),如果我们在2k(1920 * 1080) , 4k (3840 * 2160 ) 分辨率下对显示的View设置了 `captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;` 那么屏幕会牺牲一部分视频填充视图,即相机捕获的视频数据并没有完整展现在手机视图里,所以再使用我们的crop功能时,由于我们使用的是UIKit的坐标系,也就是说原点(0,0)并不是该帧图片真正像素的(0,0),而如果计算则需要写很多额外代码,所以我们可以在Crop功能下设置`captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspect;` 这样的话video视图会根据分辨率调整为显示完整视频。但是设置后如果设备是iPhoneX (比例大于16:9,X轴会缩小,黑边填充),iPad(比例小于16:9,y轴缩小,黑边填充)。 305 | 306 | ##### 按照如上解析,我们之前计算的点会出现偏差,因为相当于x或y轴会缩小一部分,而我们拿到的cropView的坐标仍然是相对于整个父View而言。 307 | 308 | 这时,如果我们通过不断更改cropView则代码量较大,所以我在这里定义了一个videoRect属性用来记录Video真正的Rect,因为当程序运行时我们可以得到屏幕宽高比例,所以通过确定宽高比可以拿到真正Video的rect,此时在后续代码中我们只需要传入videoRect的尺寸进行计算,即时是原先正常16:9的手机后面API也无须更改。 309 | 310 | #####4. 为什么用int 311 | 在软切中,我们在创建pixelBuffer时需要使用 312 | ``` 313 | CV_EXPORT CVReturn CVPixelBufferCreateWithBytes( 314 | CFAllocatorRef CV_NULLABLE allocator, 315 | size_t width, 316 | size_t height, 317 | OSType pixelFormatType, 318 | void * CV_NONNULL baseAddress, 319 | size_t bytesPerRow, 320 | CVPixelBufferReleaseBytesCallback CV_NULLABLE releaseCallback, 321 | void * CV_NULLABLE releaseRefCon, 322 | CFDictionaryRef CV_NULLABLE pixelBufferAttributes, 323 | CV_RETURNS_RETAINED_PARAMETER CVPixelBufferRef CV_NULLABLE * CV_NONNULL pixelBufferOut) 324 | ``` 325 | 这个API,我们需要将x,y的点放入baseAddress中,这里又需要使用公式` NSInteger baseAddressStart = _cropY*bytesPerRow+bytesPerPixel*_cropX; `,但是这里根据YUV 420的规则我们我们传入的X的点不能为奇数,所以我们需要`if (_cropX % 2 != 0) _cropX += 1;`,而只有整型才能求余,所以这里的点我们均定义为int,在视图展示中忽略小数点的误差。 326 | 327 | ### TODO : 328 | ##### 在硬件切割(GPU)的过程中发现 [ciContext render:ciImage toCVPixelBuffer:pixelBuffer]; 渲染时间不断增加,导致掉帧,而ciContext只初始化一次,并未发生内存泄露,如果input resolution 为 2k, 切割720P 在7plus上性能较好,其他机型和尺寸则掉帧严重。而软件切割(CPU)虽然CPU使用率相比GPU提高15%左右但是性能相对稳定,掉帧也只有在长时间直播后偶尔发生,但是CPU使用率较高。 -------------------------------------------------------------------------------- /XDXCropSampleBuffer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6429B7A51F1619610017C5A7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6429B7A41F1619610017C5A7 /* main.m */; }; 11 | 6429B7A81F1619610017C5A7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6429B7A71F1619610017C5A7 /* AppDelegate.m */; }; 12 | 6429B7AB1F1619610017C5A7 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6429B7AA1F1619610017C5A7 /* ViewController.m */; }; 13 | 6429B7AE1F1619610017C5A7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6429B7AC1F1619610017C5A7 /* Main.storyboard */; }; 14 | 6429B7B01F1619610017C5A7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6429B7AF1F1619610017C5A7 /* Assets.xcassets */; }; 15 | 6429B7B31F1619610017C5A7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6429B7B11F1619610017C5A7 /* LaunchScreen.storyboard */; }; 16 | 6429B7BE1F1619610017C5A7 /* XDXCropSampleBufferTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6429B7BD1F1619610017C5A7 /* XDXCropSampleBufferTests.m */; }; 17 | 6429B7C91F1619610017C5A7 /* XDXCropSampleBufferUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6429B7C81F1619610017C5A7 /* XDXCropSampleBufferUITests.m */; }; 18 | 6429B7D81F1619F10017C5A7 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6429B7D71F1619F10017C5A7 /* AVFoundation.framework */; }; 19 | 6429B7DB1F1627380017C5A7 /* XDXCropView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6429B7DA1F1627380017C5A7 /* XDXCropView.m */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | 6429B7BA1F1619610017C5A7 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = 6429B7981F1619610017C5A7 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = 6429B79F1F1619610017C5A7; 28 | remoteInfo = XDXCropSampleBuffer; 29 | }; 30 | 6429B7C51F1619610017C5A7 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = 6429B7981F1619610017C5A7 /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = 6429B79F1F1619610017C5A7; 35 | remoteInfo = XDXCropSampleBuffer; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | 6429B7A01F1619610017C5A7 /* XDXCropSampleBuffer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XDXCropSampleBuffer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 6429B7A41F1619610017C5A7 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 42 | 6429B7A61F1619610017C5A7 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 43 | 6429B7A71F1619610017C5A7 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 44 | 6429B7A91F1619610017C5A7 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 45 | 6429B7AA1F1619610017C5A7 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 46 | 6429B7AD1F1619610017C5A7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 47 | 6429B7AF1F1619610017C5A7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 48 | 6429B7B21F1619610017C5A7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 49 | 6429B7B41F1619610017C5A7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50 | 6429B7B91F1619610017C5A7 /* XDXCropSampleBufferTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XDXCropSampleBufferTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 6429B7BD1F1619610017C5A7 /* XDXCropSampleBufferTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XDXCropSampleBufferTests.m; sourceTree = ""; }; 52 | 6429B7BF1F1619610017C5A7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | 6429B7C41F1619610017C5A7 /* XDXCropSampleBufferUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XDXCropSampleBufferUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 6429B7C81F1619610017C5A7 /* XDXCropSampleBufferUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XDXCropSampleBufferUITests.m; sourceTree = ""; }; 55 | 6429B7CA1F1619610017C5A7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | 6429B7D71F1619F10017C5A7 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 57 | 6429B7D91F1627380017C5A7 /* XDXCropView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XDXCropView.h; sourceTree = ""; }; 58 | 6429B7DA1F1627380017C5A7 /* XDXCropView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XDXCropView.m; sourceTree = ""; }; 59 | /* End PBXFileReference section */ 60 | 61 | /* Begin PBXFrameworksBuildPhase section */ 62 | 6429B79D1F1619610017C5A7 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | 6429B7D81F1619F10017C5A7 /* AVFoundation.framework in Frameworks */, 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | 6429B7B61F1619610017C5A7 /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | 6429B7C11F1619610017C5A7 /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | ); 82 | runOnlyForDeploymentPostprocessing = 0; 83 | }; 84 | /* End PBXFrameworksBuildPhase section */ 85 | 86 | /* Begin PBXGroup section */ 87 | 6429B7971F1619610017C5A7 = { 88 | isa = PBXGroup; 89 | children = ( 90 | 6429B7A21F1619610017C5A7 /* XDXCropSampleBuffer */, 91 | 6429B7BC1F1619610017C5A7 /* XDXCropSampleBufferTests */, 92 | 6429B7C71F1619610017C5A7 /* XDXCropSampleBufferUITests */, 93 | 6429B7A11F1619610017C5A7 /* Products */, 94 | 6429B7D61F1619F10017C5A7 /* Frameworks */, 95 | ); 96 | sourceTree = ""; 97 | }; 98 | 6429B7A11F1619610017C5A7 /* Products */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 6429B7A01F1619610017C5A7 /* XDXCropSampleBuffer.app */, 102 | 6429B7B91F1619610017C5A7 /* XDXCropSampleBufferTests.xctest */, 103 | 6429B7C41F1619610017C5A7 /* XDXCropSampleBufferUITests.xctest */, 104 | ); 105 | name = Products; 106 | sourceTree = ""; 107 | }; 108 | 6429B7A21F1619610017C5A7 /* XDXCropSampleBuffer */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 6429B7A61F1619610017C5A7 /* AppDelegate.h */, 112 | 6429B7A71F1619610017C5A7 /* AppDelegate.m */, 113 | 6429B7A91F1619610017C5A7 /* ViewController.h */, 114 | 6429B7AA1F1619610017C5A7 /* ViewController.m */, 115 | 6429B7AC1F1619610017C5A7 /* Main.storyboard */, 116 | 6429B7AF1F1619610017C5A7 /* Assets.xcassets */, 117 | 6429B7B11F1619610017C5A7 /* LaunchScreen.storyboard */, 118 | 6429B7B41F1619610017C5A7 /* Info.plist */, 119 | 6429B7A31F1619610017C5A7 /* Supporting Files */, 120 | 6429B7D91F1627380017C5A7 /* XDXCropView.h */, 121 | 6429B7DA1F1627380017C5A7 /* XDXCropView.m */, 122 | ); 123 | path = XDXCropSampleBuffer; 124 | sourceTree = ""; 125 | }; 126 | 6429B7A31F1619610017C5A7 /* Supporting Files */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 6429B7A41F1619610017C5A7 /* main.m */, 130 | ); 131 | name = "Supporting Files"; 132 | sourceTree = ""; 133 | }; 134 | 6429B7BC1F1619610017C5A7 /* XDXCropSampleBufferTests */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 6429B7BD1F1619610017C5A7 /* XDXCropSampleBufferTests.m */, 138 | 6429B7BF1F1619610017C5A7 /* Info.plist */, 139 | ); 140 | path = XDXCropSampleBufferTests; 141 | sourceTree = ""; 142 | }; 143 | 6429B7C71F1619610017C5A7 /* XDXCropSampleBufferUITests */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 6429B7C81F1619610017C5A7 /* XDXCropSampleBufferUITests.m */, 147 | 6429B7CA1F1619610017C5A7 /* Info.plist */, 148 | ); 149 | path = XDXCropSampleBufferUITests; 150 | sourceTree = ""; 151 | }; 152 | 6429B7D61F1619F10017C5A7 /* Frameworks */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | 6429B7D71F1619F10017C5A7 /* AVFoundation.framework */, 156 | ); 157 | name = Frameworks; 158 | sourceTree = ""; 159 | }; 160 | /* End PBXGroup section */ 161 | 162 | /* Begin PBXNativeTarget section */ 163 | 6429B79F1F1619610017C5A7 /* XDXCropSampleBuffer */ = { 164 | isa = PBXNativeTarget; 165 | buildConfigurationList = 6429B7CD1F1619610017C5A7 /* Build configuration list for PBXNativeTarget "XDXCropSampleBuffer" */; 166 | buildPhases = ( 167 | 6429B79C1F1619610017C5A7 /* Sources */, 168 | 6429B79D1F1619610017C5A7 /* Frameworks */, 169 | 6429B79E1F1619610017C5A7 /* Resources */, 170 | ); 171 | buildRules = ( 172 | ); 173 | dependencies = ( 174 | ); 175 | name = XDXCropSampleBuffer; 176 | productName = XDXCropSampleBuffer; 177 | productReference = 6429B7A01F1619610017C5A7 /* XDXCropSampleBuffer.app */; 178 | productType = "com.apple.product-type.application"; 179 | }; 180 | 6429B7B81F1619610017C5A7 /* XDXCropSampleBufferTests */ = { 181 | isa = PBXNativeTarget; 182 | buildConfigurationList = 6429B7D01F1619610017C5A7 /* Build configuration list for PBXNativeTarget "XDXCropSampleBufferTests" */; 183 | buildPhases = ( 184 | 6429B7B51F1619610017C5A7 /* Sources */, 185 | 6429B7B61F1619610017C5A7 /* Frameworks */, 186 | 6429B7B71F1619610017C5A7 /* Resources */, 187 | ); 188 | buildRules = ( 189 | ); 190 | dependencies = ( 191 | 6429B7BB1F1619610017C5A7 /* PBXTargetDependency */, 192 | ); 193 | name = XDXCropSampleBufferTests; 194 | productName = XDXCropSampleBufferTests; 195 | productReference = 6429B7B91F1619610017C5A7 /* XDXCropSampleBufferTests.xctest */; 196 | productType = "com.apple.product-type.bundle.unit-test"; 197 | }; 198 | 6429B7C31F1619610017C5A7 /* XDXCropSampleBufferUITests */ = { 199 | isa = PBXNativeTarget; 200 | buildConfigurationList = 6429B7D31F1619610017C5A7 /* Build configuration list for PBXNativeTarget "XDXCropSampleBufferUITests" */; 201 | buildPhases = ( 202 | 6429B7C01F1619610017C5A7 /* Sources */, 203 | 6429B7C11F1619610017C5A7 /* Frameworks */, 204 | 6429B7C21F1619610017C5A7 /* Resources */, 205 | ); 206 | buildRules = ( 207 | ); 208 | dependencies = ( 209 | 6429B7C61F1619610017C5A7 /* PBXTargetDependency */, 210 | ); 211 | name = XDXCropSampleBufferUITests; 212 | productName = XDXCropSampleBufferUITests; 213 | productReference = 6429B7C41F1619610017C5A7 /* XDXCropSampleBufferUITests.xctest */; 214 | productType = "com.apple.product-type.bundle.ui-testing"; 215 | }; 216 | /* End PBXNativeTarget section */ 217 | 218 | /* Begin PBXProject section */ 219 | 6429B7981F1619610017C5A7 /* Project object */ = { 220 | isa = PBXProject; 221 | attributes = { 222 | LastUpgradeCheck = 0830; 223 | ORGANIZATIONNAME = demon; 224 | TargetAttributes = { 225 | 6429B79F1F1619610017C5A7 = { 226 | CreatedOnToolsVersion = 8.3.3; 227 | DevelopmentTeam = YDEVQQ22JR; 228 | ProvisioningStyle = Automatic; 229 | }; 230 | 6429B7B81F1619610017C5A7 = { 231 | CreatedOnToolsVersion = 8.3.3; 232 | ProvisioningStyle = Automatic; 233 | TestTargetID = 6429B79F1F1619610017C5A7; 234 | }; 235 | 6429B7C31F1619610017C5A7 = { 236 | CreatedOnToolsVersion = 8.3.3; 237 | ProvisioningStyle = Automatic; 238 | TestTargetID = 6429B79F1F1619610017C5A7; 239 | }; 240 | }; 241 | }; 242 | buildConfigurationList = 6429B79B1F1619610017C5A7 /* Build configuration list for PBXProject "XDXCropSampleBuffer" */; 243 | compatibilityVersion = "Xcode 3.2"; 244 | developmentRegion = English; 245 | hasScannedForEncodings = 0; 246 | knownRegions = ( 247 | en, 248 | Base, 249 | ); 250 | mainGroup = 6429B7971F1619610017C5A7; 251 | productRefGroup = 6429B7A11F1619610017C5A7 /* Products */; 252 | projectDirPath = ""; 253 | projectRoot = ""; 254 | targets = ( 255 | 6429B79F1F1619610017C5A7 /* XDXCropSampleBuffer */, 256 | 6429B7B81F1619610017C5A7 /* XDXCropSampleBufferTests */, 257 | 6429B7C31F1619610017C5A7 /* XDXCropSampleBufferUITests */, 258 | ); 259 | }; 260 | /* End PBXProject section */ 261 | 262 | /* Begin PBXResourcesBuildPhase section */ 263 | 6429B79E1F1619610017C5A7 /* Resources */ = { 264 | isa = PBXResourcesBuildPhase; 265 | buildActionMask = 2147483647; 266 | files = ( 267 | 6429B7B31F1619610017C5A7 /* LaunchScreen.storyboard in Resources */, 268 | 6429B7B01F1619610017C5A7 /* Assets.xcassets in Resources */, 269 | 6429B7AE1F1619610017C5A7 /* Main.storyboard in Resources */, 270 | ); 271 | runOnlyForDeploymentPostprocessing = 0; 272 | }; 273 | 6429B7B71F1619610017C5A7 /* Resources */ = { 274 | isa = PBXResourcesBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | }; 280 | 6429B7C21F1619610017C5A7 /* Resources */ = { 281 | isa = PBXResourcesBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | }; 287 | /* End PBXResourcesBuildPhase section */ 288 | 289 | /* Begin PBXSourcesBuildPhase section */ 290 | 6429B79C1F1619610017C5A7 /* Sources */ = { 291 | isa = PBXSourcesBuildPhase; 292 | buildActionMask = 2147483647; 293 | files = ( 294 | 6429B7AB1F1619610017C5A7 /* ViewController.m in Sources */, 295 | 6429B7A81F1619610017C5A7 /* AppDelegate.m in Sources */, 296 | 6429B7A51F1619610017C5A7 /* main.m in Sources */, 297 | 6429B7DB1F1627380017C5A7 /* XDXCropView.m in Sources */, 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | }; 301 | 6429B7B51F1619610017C5A7 /* Sources */ = { 302 | isa = PBXSourcesBuildPhase; 303 | buildActionMask = 2147483647; 304 | files = ( 305 | 6429B7BE1F1619610017C5A7 /* XDXCropSampleBufferTests.m in Sources */, 306 | ); 307 | runOnlyForDeploymentPostprocessing = 0; 308 | }; 309 | 6429B7C01F1619610017C5A7 /* Sources */ = { 310 | isa = PBXSourcesBuildPhase; 311 | buildActionMask = 2147483647; 312 | files = ( 313 | 6429B7C91F1619610017C5A7 /* XDXCropSampleBufferUITests.m in Sources */, 314 | ); 315 | runOnlyForDeploymentPostprocessing = 0; 316 | }; 317 | /* End PBXSourcesBuildPhase section */ 318 | 319 | /* Begin PBXTargetDependency section */ 320 | 6429B7BB1F1619610017C5A7 /* PBXTargetDependency */ = { 321 | isa = PBXTargetDependency; 322 | target = 6429B79F1F1619610017C5A7 /* XDXCropSampleBuffer */; 323 | targetProxy = 6429B7BA1F1619610017C5A7 /* PBXContainerItemProxy */; 324 | }; 325 | 6429B7C61F1619610017C5A7 /* PBXTargetDependency */ = { 326 | isa = PBXTargetDependency; 327 | target = 6429B79F1F1619610017C5A7 /* XDXCropSampleBuffer */; 328 | targetProxy = 6429B7C51F1619610017C5A7 /* PBXContainerItemProxy */; 329 | }; 330 | /* End PBXTargetDependency section */ 331 | 332 | /* Begin PBXVariantGroup section */ 333 | 6429B7AC1F1619610017C5A7 /* Main.storyboard */ = { 334 | isa = PBXVariantGroup; 335 | children = ( 336 | 6429B7AD1F1619610017C5A7 /* Base */, 337 | ); 338 | name = Main.storyboard; 339 | sourceTree = ""; 340 | }; 341 | 6429B7B11F1619610017C5A7 /* LaunchScreen.storyboard */ = { 342 | isa = PBXVariantGroup; 343 | children = ( 344 | 6429B7B21F1619610017C5A7 /* Base */, 345 | ); 346 | name = LaunchScreen.storyboard; 347 | sourceTree = ""; 348 | }; 349 | /* End PBXVariantGroup section */ 350 | 351 | /* Begin XCBuildConfiguration section */ 352 | 6429B7CB1F1619610017C5A7 /* Debug */ = { 353 | isa = XCBuildConfiguration; 354 | buildSettings = { 355 | ALWAYS_SEARCH_USER_PATHS = NO; 356 | CLANG_ANALYZER_NONNULL = YES; 357 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 358 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 359 | CLANG_CXX_LIBRARY = "libc++"; 360 | CLANG_ENABLE_MODULES = YES; 361 | CLANG_ENABLE_OBJC_ARC = YES; 362 | CLANG_WARN_BOOL_CONVERSION = YES; 363 | CLANG_WARN_CONSTANT_CONVERSION = YES; 364 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 365 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 366 | CLANG_WARN_EMPTY_BODY = YES; 367 | CLANG_WARN_ENUM_CONVERSION = YES; 368 | CLANG_WARN_INFINITE_RECURSION = YES; 369 | CLANG_WARN_INT_CONVERSION = YES; 370 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 371 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 372 | CLANG_WARN_UNREACHABLE_CODE = YES; 373 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 374 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 375 | COPY_PHASE_STRIP = NO; 376 | DEBUG_INFORMATION_FORMAT = dwarf; 377 | ENABLE_STRICT_OBJC_MSGSEND = YES; 378 | ENABLE_TESTABILITY = YES; 379 | GCC_C_LANGUAGE_STANDARD = gnu99; 380 | GCC_DYNAMIC_NO_PIC = NO; 381 | GCC_NO_COMMON_BLOCKS = YES; 382 | GCC_OPTIMIZATION_LEVEL = 0; 383 | GCC_PREPROCESSOR_DEFINITIONS = ( 384 | "DEBUG=1", 385 | "$(inherited)", 386 | ); 387 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 388 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 389 | GCC_WARN_UNDECLARED_SELECTOR = YES; 390 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 391 | GCC_WARN_UNUSED_FUNCTION = YES; 392 | GCC_WARN_UNUSED_VARIABLE = YES; 393 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 394 | MTL_ENABLE_DEBUG_INFO = YES; 395 | ONLY_ACTIVE_ARCH = YES; 396 | SDKROOT = iphoneos; 397 | TARGETED_DEVICE_FAMILY = "1,2"; 398 | }; 399 | name = Debug; 400 | }; 401 | 6429B7CC1F1619610017C5A7 /* Release */ = { 402 | isa = XCBuildConfiguration; 403 | buildSettings = { 404 | ALWAYS_SEARCH_USER_PATHS = NO; 405 | CLANG_ANALYZER_NONNULL = YES; 406 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 407 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 408 | CLANG_CXX_LIBRARY = "libc++"; 409 | CLANG_ENABLE_MODULES = YES; 410 | CLANG_ENABLE_OBJC_ARC = YES; 411 | CLANG_WARN_BOOL_CONVERSION = YES; 412 | CLANG_WARN_CONSTANT_CONVERSION = YES; 413 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 414 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 415 | CLANG_WARN_EMPTY_BODY = YES; 416 | CLANG_WARN_ENUM_CONVERSION = YES; 417 | CLANG_WARN_INFINITE_RECURSION = YES; 418 | CLANG_WARN_INT_CONVERSION = YES; 419 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 420 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 421 | CLANG_WARN_UNREACHABLE_CODE = YES; 422 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 423 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 424 | COPY_PHASE_STRIP = NO; 425 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 426 | ENABLE_NS_ASSERTIONS = NO; 427 | ENABLE_STRICT_OBJC_MSGSEND = YES; 428 | GCC_C_LANGUAGE_STANDARD = gnu99; 429 | GCC_NO_COMMON_BLOCKS = YES; 430 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 431 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 432 | GCC_WARN_UNDECLARED_SELECTOR = YES; 433 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 434 | GCC_WARN_UNUSED_FUNCTION = YES; 435 | GCC_WARN_UNUSED_VARIABLE = YES; 436 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 437 | MTL_ENABLE_DEBUG_INFO = NO; 438 | SDKROOT = iphoneos; 439 | TARGETED_DEVICE_FAMILY = "1,2"; 440 | VALIDATE_PRODUCT = YES; 441 | }; 442 | name = Release; 443 | }; 444 | 6429B7CE1F1619610017C5A7 /* Debug */ = { 445 | isa = XCBuildConfiguration; 446 | buildSettings = { 447 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 448 | DEVELOPMENT_TEAM = YDEVQQ22JR; 449 | INFOPLIST_FILE = XDXCropSampleBuffer/Info.plist; 450 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 451 | PRODUCT_BUNDLE_IDENTIFIER = com.xdx.XDXCropSampleBuffer; 452 | PRODUCT_NAME = "$(TARGET_NAME)"; 453 | }; 454 | name = Debug; 455 | }; 456 | 6429B7CF1F1619610017C5A7 /* Release */ = { 457 | isa = XCBuildConfiguration; 458 | buildSettings = { 459 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 460 | DEVELOPMENT_TEAM = YDEVQQ22JR; 461 | INFOPLIST_FILE = XDXCropSampleBuffer/Info.plist; 462 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 463 | PRODUCT_BUNDLE_IDENTIFIER = com.xdx.XDXCropSampleBuffer; 464 | PRODUCT_NAME = "$(TARGET_NAME)"; 465 | }; 466 | name = Release; 467 | }; 468 | 6429B7D11F1619610017C5A7 /* Debug */ = { 469 | isa = XCBuildConfiguration; 470 | buildSettings = { 471 | BUNDLE_LOADER = "$(TEST_HOST)"; 472 | INFOPLIST_FILE = XDXCropSampleBufferTests/Info.plist; 473 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 474 | PRODUCT_BUNDLE_IDENTIFIER = com.xdx.XDXCropSampleBufferTests; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/XDXCropSampleBuffer.app/XDXCropSampleBuffer"; 477 | }; 478 | name = Debug; 479 | }; 480 | 6429B7D21F1619610017C5A7 /* Release */ = { 481 | isa = XCBuildConfiguration; 482 | buildSettings = { 483 | BUNDLE_LOADER = "$(TEST_HOST)"; 484 | INFOPLIST_FILE = XDXCropSampleBufferTests/Info.plist; 485 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 486 | PRODUCT_BUNDLE_IDENTIFIER = com.xdx.XDXCropSampleBufferTests; 487 | PRODUCT_NAME = "$(TARGET_NAME)"; 488 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/XDXCropSampleBuffer.app/XDXCropSampleBuffer"; 489 | }; 490 | name = Release; 491 | }; 492 | 6429B7D41F1619610017C5A7 /* Debug */ = { 493 | isa = XCBuildConfiguration; 494 | buildSettings = { 495 | INFOPLIST_FILE = XDXCropSampleBufferUITests/Info.plist; 496 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 497 | PRODUCT_BUNDLE_IDENTIFIER = com.xdx.XDXCropSampleBufferUITests; 498 | PRODUCT_NAME = "$(TARGET_NAME)"; 499 | TEST_TARGET_NAME = XDXCropSampleBuffer; 500 | }; 501 | name = Debug; 502 | }; 503 | 6429B7D51F1619610017C5A7 /* Release */ = { 504 | isa = XCBuildConfiguration; 505 | buildSettings = { 506 | INFOPLIST_FILE = XDXCropSampleBufferUITests/Info.plist; 507 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 508 | PRODUCT_BUNDLE_IDENTIFIER = com.xdx.XDXCropSampleBufferUITests; 509 | PRODUCT_NAME = "$(TARGET_NAME)"; 510 | TEST_TARGET_NAME = XDXCropSampleBuffer; 511 | }; 512 | name = Release; 513 | }; 514 | /* End XCBuildConfiguration section */ 515 | 516 | /* Begin XCConfigurationList section */ 517 | 6429B79B1F1619610017C5A7 /* Build configuration list for PBXProject "XDXCropSampleBuffer" */ = { 518 | isa = XCConfigurationList; 519 | buildConfigurations = ( 520 | 6429B7CB1F1619610017C5A7 /* Debug */, 521 | 6429B7CC1F1619610017C5A7 /* Release */, 522 | ); 523 | defaultConfigurationIsVisible = 0; 524 | defaultConfigurationName = Release; 525 | }; 526 | 6429B7CD1F1619610017C5A7 /* Build configuration list for PBXNativeTarget "XDXCropSampleBuffer" */ = { 527 | isa = XCConfigurationList; 528 | buildConfigurations = ( 529 | 6429B7CE1F1619610017C5A7 /* Debug */, 530 | 6429B7CF1F1619610017C5A7 /* Release */, 531 | ); 532 | defaultConfigurationIsVisible = 0; 533 | defaultConfigurationName = Release; 534 | }; 535 | 6429B7D01F1619610017C5A7 /* Build configuration list for PBXNativeTarget "XDXCropSampleBufferTests" */ = { 536 | isa = XCConfigurationList; 537 | buildConfigurations = ( 538 | 6429B7D11F1619610017C5A7 /* Debug */, 539 | 6429B7D21F1619610017C5A7 /* Release */, 540 | ); 541 | defaultConfigurationIsVisible = 0; 542 | defaultConfigurationName = Release; 543 | }; 544 | 6429B7D31F1619610017C5A7 /* Build configuration list for PBXNativeTarget "XDXCropSampleBufferUITests" */ = { 545 | isa = XCConfigurationList; 546 | buildConfigurations = ( 547 | 6429B7D41F1619610017C5A7 /* Debug */, 548 | 6429B7D51F1619610017C5A7 /* Release */, 549 | ); 550 | defaultConfigurationIsVisible = 0; 551 | defaultConfigurationName = Release; 552 | }; 553 | /* End XCConfigurationList section */ 554 | }; 555 | rootObject = 6429B7981F1619610017C5A7 /* Project object */; 556 | } 557 | -------------------------------------------------------------------------------- /XDXCropSampleBuffer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /XDXCropSampleBuffer.xcodeproj/project.xcworkspace/xcuserdata/demon.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XiaoDongXie1024/Crop-sample-buffer/8146e89955d8109b5f050d090d95068857055fa6/XDXCropSampleBuffer.xcodeproj/project.xcworkspace/xcuserdata/demon.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /XDXCropSampleBuffer.xcodeproj/xcuserdata/demon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /XDXCropSampleBuffer.xcodeproj/xcuserdata/demon.xcuserdatad/xcschemes/XDXCropSampleBuffer.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /XDXCropSampleBuffer.xcodeproj/xcuserdata/demon.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | XDXCropSampleBuffer.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 6429B79F1F1619610017C5A7 16 | 17 | primary 18 | 19 | 20 | 6429B7B81F1619610017C5A7 21 | 22 | primary 23 | 24 | 25 | 6429B7C31F1619610017C5A7 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /XDXCropSampleBuffer/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // XDXCropSampleBuffer 4 | // 5 | // Created by demon on 12/07/2017. 6 | // Copyright © 2017 demon. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /XDXCropSampleBuffer/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // XDXCropSampleBuffer 4 | // 5 | // Created by demon on 12/07/2017. 6 | // Copyright © 2017 demon. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | 21 | 22 | 23 | return YES; 24 | } 25 | 26 | 27 | - (void)applicationWillResignActive:(UIApplication *)application { 28 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 29 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 30 | } 31 | 32 | 33 | - (void)applicationDidEnterBackground:(UIApplication *)application { 34 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 35 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 36 | } 37 | 38 | 39 | - (void)applicationWillEnterForeground:(UIApplication *)application { 40 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 41 | } 42 | 43 | 44 | - (void)applicationDidBecomeActive:(UIApplication *)application { 45 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 46 | } 47 | 48 | 49 | - (void)applicationWillTerminate:(UIApplication *)application { 50 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 51 | } 52 | 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /XDXCropSampleBuffer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /XDXCropSampleBuffer/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /XDXCropSampleBuffer/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /XDXCropSampleBuffer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSCameraUsageDescription 24 | OK 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationLandscapeLeft 36 | UIInterfaceOrientationLandscapeRight 37 | 38 | UISupportedInterfaceOrientations~ipad 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationPortraitUpsideDown 42 | UIInterfaceOrientationLandscapeLeft 43 | UIInterfaceOrientationLandscapeRight 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /XDXCropSampleBuffer/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // XDXCropSampleBuffer 4 | // 5 | // Created by demon on 12/07/2017. 6 | // Copyright © 2017 demon. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /XDXCropSampleBuffer/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // XDXCropSampleBuffer 4 | // 5 | // Created by demon on 12/07/2017. 6 | // Copyright © 2017 demon. All rights reserved. 7 | // 8 | 9 | /*************************************************************************************************************************************/ 10 | 11 | // 注意 : 本Demo中将界面只允许竖屏,长按即为捕捉画面,捕捉画面的数据在AVCaptureVideoDataOutputSampleBufferDelegate 中的 cropSampleBuffer 数据结构中,裁剪代码在方法- (CMSampleBufferRef)cropSampleBuffer:(CMSampleBufferRef)buffer withCropRect:(CGRect)cropRect 中,其余为初始化相机界面与长按点击事件的方法。 12 | 13 | // 本文中注释为Log4cplus 代码,如果你的机器有可以打开注释,如果没有可以自行替换为NSLog获取一些信息 14 | 15 | // 本文具体解析请参考: GitHub : https://github.com/ChengyangLi/Crop-sample-buffer 16 | // 博客 : https://chengyangli.github.io/2017/07/12/cropSampleBuffer/ 17 | // 简书 : http://www.jianshu.com/p/ac79a80f1af2 18 | 19 | /*************************************************************************************************************************************/ 20 | 21 | #import "ViewController.h" 22 | #import 23 | #import "XDXCropView.h" 24 | 25 | #define kScreenWidth [UIScreen mainScreen].bounds.size.width 26 | #define kScreenHeight [UIScreen mainScreen].bounds.size.height 27 | 28 | #define currentResolutionW 1920 29 | #define currentResolutionH 1080 30 | #define currentResolution AVCaptureSessionPreset1920x1080 31 | 32 | // 截取cropView的大小 33 | int g_width_size = 1280; 34 | int g_height_size = 720; 35 | 36 | @interface ViewController () 37 | { 38 | CIContext *_ciContext; 39 | } 40 | @property (nonatomic, strong) AVCaptureSession *captureSession; 41 | @property (nonatomic, strong) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer; 42 | @property (nonatomic, strong) XDXCropView *cropView; 43 | @property (nonatomic, assign) BOOL isOpenGPU; 44 | 45 | @end 46 | 47 | @implementation ViewController 48 | 49 | - (void)viewDidLoad { 50 | [super viewDidLoad]; 51 | 52 | // 设置始终横屏 53 | [self setScreenCross]; 54 | 55 | // 初始化相机Preview相关参数 56 | [self initCapture]; 57 | 58 | // 初始化CropView的参数 59 | /* 初始化CropView的参数 60 | 注意:本Demo中如果设置4K需要手机硬件设备的支持,即iPhone 6s以上才支持,使用GPU进行切割如果是4K画面切2K画面,目前尚存在问题,亲测只能维持5分钟开始严重掉帧,博客里有详细介绍原因,这里不过多说明。 61 | */ 62 | self.isOpenGPU = NO; 63 | self.cropView = [[XDXCropView alloc] initWithOpen4K:NO OpenGpu:self.isOpenGPU cropWidth:g_width_size cropHeight:g_height_size screenResolutionW:currentResolutionW screenResolutionH:currentResolutionH]; 64 | [self.cropView isEnableCrop:YES session:_captureSession captureLayer:_captureVideoPreviewLayer mainView:self.view]; 65 | // self.cropView.center = self.view.center; 66 | self.cropView.backgroundColor = [UIColor clearColor]; 67 | [self.view bringSubviewToFront:_cropView]; 68 | 69 | UILongPressGestureRecognizer *pressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressed:)]; 70 | [self.view addGestureRecognizer:pressGesture]; 71 | } 72 | 73 | 74 | - (void)longPressed:(UITapGestureRecognizer *)recognizer { 75 | CGPoint currentPoint = [recognizer locationInView:recognizer.view]; 76 | [self.cropView longPressedWithCurrentPoint:currentPoint 77 | isOpenGpu:self.isOpenGPU]; 78 | } 79 | 80 | - (void)setScreenCross { 81 | if([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) { 82 | SEL selector = NSSelectorFromString(@"setOrientation:"); 83 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]]; 84 | [invocation setSelector:selector]; 85 | [invocation setTarget:[UIDevice currentDevice]]; 86 | int val = UIInterfaceOrientationLandscapeLeft;//横屏 87 | [invocation setArgument:&val atIndex:2]; 88 | [invocation invoke]; 89 | } 90 | } 91 | 92 | - (void)initCapture 93 | { 94 | // 获取后置摄像头设备 95 | AVCaptureDevice *inputDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 96 | // 创建输入数据对象 97 | AVCaptureDeviceInput *captureInput = [AVCaptureDeviceInput deviceInputWithDevice:inputDevice error:nil]; 98 | if (!captureInput) return; 99 | 100 | // 创建一个视频输出对象 101 | AVCaptureVideoDataOutput *captureOutput = [[AVCaptureVideoDataOutput alloc] init]; 102 | [captureOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()]; 103 | 104 | NSString *key = (NSString *)kCVPixelBufferPixelFormatTypeKey; 105 | NSNumber *value = [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA]; 106 | NSDictionary *videoSettings = [NSDictionary dictionaryWithObject:value forKey:key]; 107 | 108 | [captureOutput setVideoSettings:videoSettings]; 109 | 110 | 111 | self.captureSession = [[AVCaptureSession alloc] init]; 112 | NSString *preset; 113 | 114 | #warning 注意,iPhone 6s以上设备可以设置为2K,若测试设备为6S以下则需要降低分辨率,但本APP中只支持16:9的分辨率 115 | if (!preset) preset = AVCaptureSessionPreset1920x1080; 116 | 117 | if ([_captureSession canSetSessionPreset:preset]) { 118 | self.captureSession.sessionPreset = preset; 119 | }else { 120 | self.captureSession.sessionPreset = AVCaptureSessionPresetHigh; 121 | } 122 | 123 | if ([self.captureSession canAddInput:captureInput]) { 124 | [self.captureSession addInput:captureInput]; 125 | } 126 | if ([self.captureSession canAddOutput:captureOutput]) { 127 | [self.captureSession addOutput:captureOutput]; 128 | } 129 | 130 | // 创建视频预览图层 131 | if (!self.captureVideoPreviewLayer) { 132 | self.captureVideoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession]; 133 | } 134 | 135 | self.captureVideoPreviewLayer.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight); 136 | self.captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; 137 | if([[self.captureVideoPreviewLayer connection] isVideoOrientationSupported]) 138 | { 139 | [self.captureVideoPreviewLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeRight]; 140 | } 141 | 142 | [self.view.layer addSublayer:self.captureVideoPreviewLayer]; 143 | [self.captureSession startRunning]; 144 | } 145 | 146 | #pragma mark ------------------AVCaptureVideoDataOutputSampleBufferDelegate-------------------------------- 147 | // Called whenever an AVCaptureVideoDataOutput instance outputs a new video frame. 每产生一帧视频帧时调用一次 148 | - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { 149 | CMSampleBufferRef cropSampleBuffer; 150 | 151 | #warning 两种切割方式任选其一,GPU切割性能较好,CPU切割取决于设备,一般时间长会掉帧。 152 | if (self.isOpenGPU) { 153 | cropSampleBuffer = [self.cropView cropSampleBufferByHardware:sampleBuffer]; 154 | }else { 155 | cropSampleBuffer = [self.cropView cropSampleBufferBySoftware:sampleBuffer]; 156 | } 157 | 158 | // 使用完后必须显式release,不在iOS自动回收范围 159 | CFRelease(cropSampleBuffer); 160 | } 161 | 162 | @end 163 | -------------------------------------------------------------------------------- /XDXCropSampleBuffer/XDXCropView.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDXCropView.h 3 | // XDXCropSampleBuffer 4 | // 5 | // Created by demon on 12/07/2017. 6 | // Copyright © 2017 demon. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | /** 14 | 区分当前设备屏幕比例 15 | - XDXCurrentScaleEqual: 当前屏幕比支持 16 : 9 16 | - XDXCurrentScaleBigger: 当前屏幕比大于 16 : 9 17 | - XDXCurrentScaleSmaller: 当前屏幕比小于 16 : 9 18 | */ 19 | typedef NS_ENUM(NSUInteger, XDXCurrentDeviceScale) { 20 | XDXCurrentScaleEqual, 21 | XDXCurrentScaleBigger, 22 | XDXCurrentScaleSmaller, 23 | }; 24 | 25 | @interface XDXCropView : UIView 26 | 27 | /** 28 | Two Condition: (Only backgroud resolution is 2k ) 29 | 1. when the device size is 16 : 9, the size is kScreenWidth,kScreenHeight 30 | 2. when the device size is not 16 : 9, the origin.y and size.height need to change. 31 | */ 32 | @property (nonatomic, assign) CGRect videoRect; 33 | @property (nonatomic, assign) XDXCurrentDeviceScale currentDeviceScale; 34 | @property (nonatomic, assign) int currentResolutionW; 35 | @property (nonatomic, assign) int currentResolutionH; 36 | 37 | @property (nonatomic, assign, getter=isDescendantOfMainView)BOOL descendantOfMainView; 38 | @property (nonatomic, assign, getter=isOpen4K) BOOL open4K; 39 | @property (nonatomic, assign, getter=isOpenGPU) BOOL openGpu; 40 | 41 | 42 | /** 43 | 使用CPU / GPU 进行切割 44 | */ 45 | - (CMSampleBufferRef)cropSampleBufferBySoftware:(CMSampleBufferRef)sampleBuffer; 46 | - (CMSampleBufferRef)cropSampleBufferByHardware:(CMSampleBufferRef)buffer; 47 | 48 | 49 | /** 50 | 打开或关闭Crop功能 51 | 52 | @param enableCrop 打开或关闭选项 53 | @param session 视频session 54 | @param captureVideoPreviewLayer 视频preview层 55 | @param mainView 主控制器的view 56 | */ 57 | - (void)isEnableCrop:(BOOL)enableCrop session:(AVCaptureSession *)session captureLayer:(AVCaptureVideoPreviewLayer *)captureVideoPreviewLayer mainView:(UIView *)mainView; 58 | 59 | 60 | /** 61 | 初始化CropView的全能初始化方法 62 | 63 | @param open4K 是否打开4K 64 | @param openGpu 是否使用GPU,否则使用CPU 65 | @param cropWidth cropView的宽度 66 | @param cropHeight cropView的高度 67 | @param screenResolutionW 当前屏幕分辨率的宽 68 | @param screenResolutionH 当前屏幕分辨率的高 69 | */ 70 | - (instancetype)initWithOpen4K:(BOOL)open4K OpenGpu:(BOOL)openGpu cropWidth:(CGFloat)cropWidth cropHeight:(CGFloat)cropHeight screenResolutionW:(int)screenResolutionW screenResolutionH:(int)screenResolutionH; 71 | 72 | 73 | /** 74 | 长按屏幕触发的操作,即移动cropView的位置 75 | */ 76 | - (void)longPressedWithCurrentPoint:(CGPoint)currentPoint isOpenGpu:(BOOL)isOpenGpu ; 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /XDXCropSampleBuffer/XDXCropView.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDXCropView.m 3 | // XDXCropSampleBuffer 4 | // 5 | // Created by demon on 12/07/2017. 6 | // Copyright © 2017 demon. All rights reserved. 7 | // 8 | 9 | #import "XDXCropView.h" 10 | 11 | #define kScreenWidth [UIScreen mainScreen].bounds.size.width 12 | #define kScreenHeight [UIScreen mainScreen].bounds.size.height 13 | 14 | extern int g_width_size; 15 | extern int g_height_size; 16 | 17 | @interface XDXCropView() 18 | 19 | @property (nonatomic, assign) int cropX; 20 | @property (nonatomic, assign) int cropY; 21 | @property (nonatomic, assign) CGFloat screenWidth; 22 | @property (nonatomic, assign) CGFloat screenHeight; 23 | @property (nonatomic, assign) CGFloat cropViewWidth; 24 | @property (nonatomic, assign) CGFloat cropViewHeight; 25 | 26 | @end 27 | 28 | @implementation XDXCropView 29 | 30 | #pragma mark - Init 31 | - (void)drawRect:(CGRect)rect { 32 | CGContextRef context =UIGraphicsGetCurrentContext(); 33 | CGContextSetStrokeColorWithColor(context, [UIColor greenColor].CGColor); 34 | // lengths的值{10,10}表示先绘制10个点,再跳过10个点,如此反复,如果把lengths值改为{10, 20, 10},则表示先绘制10个点,跳过20个点,绘制10个点,跳过10个点,再绘制20个点 35 | const CGFloat lengths[] = {10,10}; 36 | CGContextAddRect(context, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)); 37 | CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor); 38 | // lengths: 虚线是如何交替绘制 count:lengths数组的长度 39 | CGContextSetLineDash(context, 0, lengths, 2); 40 | // CGContextSetFillColorWithColor(context, [UIColor greenColor].CGColor); 41 | CGContextStrokePath(context); 42 | } 43 | 44 | - (instancetype)initWithOpen4K:(BOOL)open4K OpenGpu:(BOOL)openGpu cropWidth:(CGFloat)cropWidth cropHeight:(CGFloat)cropHeight screenResolutionW:(int)screenResolutionW screenResolutionH:(int)screenResolutionH { 45 | if (self = [super init]) { 46 | self.backgroundColor = [UIColor clearColor]; 47 | [self judgeDeviceScale]; 48 | [self updateVideoRect]; 49 | self.open4K = open4K; 50 | self.openGpu = openGpu; 51 | self.currentResolutionW = screenResolutionW; 52 | self.currentResolutionH = screenResolutionH; 53 | 54 | } 55 | return self; 56 | } 57 | 58 | #pragma mark - Main function 59 | - (void)isEnableCrop:(BOOL)enableCrop session:(AVCaptureSession *)session captureLayer:(AVCaptureVideoPreviewLayer *)captureVideoPreviewLayer mainView:(UIView *)mainView { 60 | // Start Encoder then start camera, 配置cropView相关部分最好事先停止相机,因为涉及到分辨率改变等因素,如果项目中存在encoder, 避免回调中数据变换产生问题。 61 | if (session.isRunning) [session stopRunning]; 62 | 63 | if (enableCrop) { 64 | // The device screen is not 16 : 9, So we need to reset it. 65 | if (self.currentDeviceScale != XDXCurrentScaleEqual) { 66 | if (![captureVideoPreviewLayer.videoGravity isEqual:AVLayerVideoGravityResizeAspect]) { 67 | captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspect; 68 | NSLog(@"Crop The device is not 16:9 so we need to resize aspect!"); 69 | } 70 | } 71 | 72 | if (![self isDescendantOfView:mainView]) { 73 | [mainView addSubview:self]; 74 | self.descendantOfMainView = YES; 75 | } 76 | 77 | [self updateCropViewWithParamOpen4KResolution:self.isOpen4K 78 | isOpenGpu:self.isOpenGPU]; 79 | 80 | }else { 81 | if (self.currentDeviceScale != XDXCurrentScaleEqual) { 82 | if (![captureVideoPreviewLayer.videoGravity isEqual:AVLayerVideoGravityResizeAspectFill]) { 83 | captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; 84 | NSLog(@"Crop The device is not fill screen so we need to fill screen!"); 85 | } 86 | } 87 | 88 | if ([self isDescendantOfView:mainView]) { 89 | [self removeFromSuperview]; 90 | self.descendantOfMainView = NO; 91 | } 92 | } 93 | 94 | [session startRunning]; 95 | } 96 | 97 | - (void)longPressedWithCurrentPoint:(CGPoint)currentPoint isOpenGpu:(BOOL)isOpenGpu { 98 | CGFloat currentPointX = currentPoint.x; 99 | CGFloat currentPointY = currentPoint.y; 100 | 101 | // Judge whether to exceed the boundary, 如果超出边界则进行校正 102 | if ((currentPointX - self.videoRect.origin.x) < _cropViewWidth / 2) { 103 | currentPointX = _cropViewWidth / 2 + self.videoRect.origin.x; 104 | }else if((currentPointX - self.videoRect.origin.x) > _screenWidth - _cropViewWidth / 2) { 105 | currentPointX = _screenWidth - _cropViewWidth / 2 + self.videoRect.origin.x; 106 | } 107 | 108 | if ((currentPointY - self.videoRect.origin.y) < _cropViewHeight / 2) { 109 | currentPointY = _cropViewHeight / 2 + self.videoRect.origin.y; 110 | }else if ((currentPointY - self.videoRect.origin.y) > _screenHeight - _cropViewHeight / 2) { 111 | currentPointY = self.videoRect.origin.y + _screenHeight - _cropViewHeight / 2; 112 | } 113 | 114 | self.center = CGPointMake(currentPointX, currentPointY); 115 | 116 | [self updateCropViewOriginOfResolutionWithOpenGpu:isOpenGpu]; 117 | } 118 | 119 | - (void)updateCropViewWithParamOpen4KResolution:(BOOL)isOpen4KResolution isOpenGpu:(BOOL)isOpenGpu { 120 | 121 | _screenWidth = self.videoRect.size.width; 122 | _screenHeight = self.videoRect.size.height; 123 | _cropViewWidth = _screenWidth / self.currentResolutionW * g_width_size; 124 | _cropViewHeight = _screenHeight / self.currentResolutionH * g_height_size; 125 | 126 | CGFloat cropViewCenterX = (_screenWidth - _cropViewWidth ) / 2 + self.videoRect.origin.x; 127 | CGFloat cropViewCenterY = (_screenHeight - _cropViewHeight) / 2 + self.videoRect.origin.y; 128 | self.frame = CGRectMake(cropViewCenterX, cropViewCenterY, _cropViewWidth, _cropViewHeight); 129 | 130 | [self updateCropViewOriginOfResolutionWithOpenGpu:isOpenGpu]; 131 | } 132 | 133 | - (void)updateCropViewOriginOfResolutionWithOpenGpu:(BOOL)isOpenGpu { 134 | // 使用CPU/GPU 切割坐标系不同,所以需要转换,详细请看博客 135 | _cropX = (int)(_currentResolutionW / _screenWidth * (self.frame.origin.x - self.videoRect.origin.x)); 136 | if (isOpenGpu) { 137 | _cropY = (int)(_currentResolutionH / _screenHeight * (_screenHeight - (self.frame.origin.y - self.videoRect.origin.y) - self.frame.size.height)); 138 | }else { 139 | _cropY = (int)(_currentResolutionH / _screenHeight * (self.frame.origin.y-self.videoRect.origin.y)); 140 | } 141 | 142 | NSLog(@"Crop The Crop View's x : %f, y : %f, width : %f, height : %f \n Crop Pix's X : %d, Y : %d, Width : %d, Height : %d",self.frame.origin.x, self.frame.origin.y, _cropViewWidth, _cropViewHeight, _cropX, _cropY, g_width_size, g_height_size); 143 | } 144 | 145 | - (void)judgeDeviceScale { 146 | CGFloat screenWidth = kScreenWidth > kScreenHeight ? kScreenWidth : kScreenHeight; 147 | CGFloat screenHeight = kScreenWidth > kScreenHeight ? kScreenHeight : kScreenWidth; 148 | 149 | CGFloat standardScale = 16.0 / 9.0; 150 | CGFloat currentScale = screenWidth / screenHeight; 151 | // 因为可能存在微小误差,例如iPhone 8P正好是16:9, 而iPhone 8 则有0.001以下的误差,但我们也认为它是16:9,所以这里不能完全按照16:9比较。 152 | CGFloat scaleError = 0.1; 153 | 154 | if (currentScale - standardScale > scaleError) { 155 | self.currentDeviceScale = XDXCurrentScaleBigger; 156 | }else if (standardScale - currentScale > scaleError) { 157 | self.currentDeviceScale = XDXCurrentScaleSmaller; 158 | }else { 159 | self.currentDeviceScale = XDXCurrentScaleEqual; 160 | } 161 | 162 | NSLog(@"Crop The current device scale is %lu",(unsigned long)self.currentDeviceScale); 163 | } 164 | 165 | - (void)updateVideoRect { 166 | CGFloat screenWidth = kScreenWidth > kScreenHeight ? kScreenWidth : kScreenHeight; 167 | CGFloat screenHeight = kScreenWidth > kScreenHeight ? kScreenHeight : kScreenWidth; 168 | CGFloat videoX = 0; 169 | CGFloat videoY = 0; 170 | CGFloat videoWidth = screenWidth; 171 | CGFloat videoHeight = screenHeight; 172 | 173 | switch (self.currentDeviceScale) { 174 | case XDXCurrentScaleEqual: 175 | break; 176 | 177 | case XDXCurrentScaleBigger: 178 | videoWidth = screenHeight * 16 / 9; 179 | videoX = (screenWidth - videoWidth) / 2; 180 | break; 181 | 182 | case XDXCurrentScaleSmaller: 183 | videoHeight = screenWidth * 9 / 16; 184 | videoY = (screenHeight - videoHeight) / 2; 185 | break; 186 | 187 | default: 188 | break; 189 | } 190 | 191 | self.videoRect = CGRectMake(videoX, videoY, videoWidth, videoHeight); 192 | NSLog(@"Crop The video rect is : %f - %f - %f - %f",self.videoRect.origin.x,self.videoRect.origin.y,self.videoRect.size.width,self.videoRect.size.height); 193 | } 194 | 195 | #pragma mark - Crop by CPU / GPU 196 | // software crop 197 | - (CMSampleBufferRef)cropSampleBufferBySoftware:(CMSampleBufferRef)sampleBuffer { 198 | OSStatus status; 199 | 200 | // CVPixelBufferRef pixelBuffer = [self modifyImage:buffer]; 201 | CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 202 | // Lock the image buffer 203 | CVPixelBufferLockBaseAddress(imageBuffer,0); 204 | // Get information about the image 205 | uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(imageBuffer); 206 | size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); 207 | size_t width = CVPixelBufferGetWidth(imageBuffer); 208 | // size_t height = CVPixelBufferGetHeight(imageBuffer); 209 | NSInteger bytesPerPixel = bytesPerRow/width; 210 | 211 | // YUV 420 Rule 212 | if (_cropX % 2 != 0) _cropX += 1; 213 | NSInteger baseAddressStart = _cropY*bytesPerRow+bytesPerPixel*_cropX; 214 | static NSInteger lastAddressStart = 0; 215 | lastAddressStart = baseAddressStart; 216 | 217 | // pixbuffer 与 videoInfo 只有位置变换或者切换分辨率或者相机重启时需要更新,其余情况不需要,Demo里只写了位置更新,其余情况自行添加 218 | // NSLog(@"demon pix first : %zu - %zu - %@ - %d - %d - %d -%d",width, height, self.currentResolution,_cropX,_cropY,self.currentResolutionW,self.currentResolutionH); 219 | static CVPixelBufferRef pixbuffer = NULL; 220 | static CMVideoFormatDescriptionRef videoInfo = NULL; 221 | 222 | // x,y changed need to reset pixbuffer and videoinfo 223 | if (lastAddressStart != baseAddressStart) { 224 | if (pixbuffer != NULL) { 225 | CVPixelBufferRelease(pixbuffer); 226 | pixbuffer = NULL; 227 | } 228 | 229 | if (videoInfo != NULL) { 230 | CFRelease(videoInfo); 231 | videoInfo = NULL; 232 | } 233 | } 234 | 235 | if (pixbuffer == NULL) { 236 | NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: 237 | [NSNumber numberWithBool : YES], kCVPixelBufferCGImageCompatibilityKey, 238 | [NSNumber numberWithBool : YES], kCVPixelBufferCGBitmapContextCompatibilityKey, 239 | [NSNumber numberWithInt : g_width_size], kCVPixelBufferWidthKey, 240 | [NSNumber numberWithInt : g_height_size], kCVPixelBufferHeightKey, 241 | nil]; 242 | 243 | status = CVPixelBufferCreateWithBytes(kCFAllocatorDefault, g_width_size, g_height_size, kCVPixelFormatType_32BGRA, &baseAddress[baseAddressStart], bytesPerRow, NULL, NULL, (__bridge CFDictionaryRef)options, &pixbuffer); 244 | if (status != 0) { 245 | NSLog(@"Crop CVPixelBufferCreateWithBytes error %d",(int)status); 246 | return NULL; 247 | } 248 | } 249 | 250 | CVPixelBufferUnlockBaseAddress(imageBuffer,0); 251 | 252 | CMSampleTimingInfo sampleTime = { 253 | .duration = CMSampleBufferGetDuration(sampleBuffer), 254 | .presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer), 255 | .decodeTimeStamp = CMSampleBufferGetDecodeTimeStamp(sampleBuffer) 256 | }; 257 | 258 | if (videoInfo == NULL) { 259 | status = CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixbuffer, &videoInfo); 260 | if (status != 0) NSLog(@"Crop CMVideoFormatDescriptionCreateForImageBuffer error %d",(int)status); 261 | } 262 | 263 | CMSampleBufferRef cropBuffer = NULL; 264 | status = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, pixbuffer, true, NULL, NULL, videoInfo, &sampleTime, &cropBuffer); 265 | if (status != 0) NSLog(@"Crop CMSampleBufferCreateForImageBuffer error %d",(int)status); 266 | 267 | lastAddressStart = baseAddressStart; 268 | 269 | return cropBuffer; 270 | } 271 | 272 | // hardware crop 273 | - (CMSampleBufferRef)cropSampleBufferByHardware:(CMSampleBufferRef)buffer { 274 | // a CMSampleBuffer's CVImageBuffer of media data. 275 | CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(buffer); 276 | CGRect cropRect = CGRectMake(_cropX, _cropY, g_width_size, g_height_size); 277 | // log4cplus_debug("Crop", "dropRect x: %f - y : %f - width : %zu - height : %zu", cropViewX, cropViewY, width, height); 278 | 279 | /* 280 | First, to render to a texture, you need an image that is compatible with the OpenGL texture cache. Images that were created with the camera API are already compatible and you can immediately map them for inputs. Suppose you want to create an image to render on and later read out for some other processing though. You have to have create the image with a special property. The attributes for the image must have kCVPixelBufferIOSurfacePropertiesKey as one of the keys to the dictionary. 281 | 如果要进行页面渲染,需要一个和OpenGL缓冲兼容的图像。用相机API创建的图像已经兼容,您可以马上映射他们进行输入。假设你从已有画面中截取一个新的画面,用作其他处理,你必须创建一种特殊的属性用来创建图像。对于图像的属性必须有kCVPixelBufferIOSurfacePropertiesKey 作为字典的Key.因此以下步骤不可省略 282 | */ 283 | 284 | OSStatus status; 285 | 286 | /* Only resolution has changed we need to reset pixBuffer and videoInfo so that reduce calculate count */ 287 | static CVPixelBufferRef pixbuffer = NULL; 288 | static CMVideoFormatDescriptionRef videoInfo = NULL; 289 | 290 | if (pixbuffer == NULL) { 291 | NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: 292 | [NSNumber numberWithInt:g_width_size], kCVPixelBufferWidthKey, 293 | [NSNumber numberWithInt:g_height_size], kCVPixelBufferHeightKey, nil]; 294 | status = CVPixelBufferCreate(kCFAllocatorSystemDefault, g_width_size, g_height_size, kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, (__bridge CFDictionaryRef)options, &pixbuffer); 295 | // ensures that the CVPixelBuffer is accessible in system memory. This should only be called if the base address is going to be used and the pixel data will be accessed by the CPU 296 | if (status != noErr) { 297 | NSLog(@"Crop CVPixelBufferCreate error %d",(int)status); 298 | return NULL; 299 | } 300 | } 301 | 302 | CIImage *ciImage = [CIImage imageWithCVImageBuffer:imageBuffer]; 303 | ciImage = [ciImage imageByCroppingToRect:cropRect]; 304 | // Ciimage get real image is not in the original point after excute crop. So we need to pan. 305 | ciImage = [ciImage imageByApplyingTransform:CGAffineTransformMakeTranslation(-_cropX, -_cropY)]; 306 | 307 | static CIContext *ciContext = nil; 308 | if (ciContext == nil) { 309 | // NSMutableDictionary *options = [[NSMutableDictionary alloc] init]; 310 | // [options setObject:[NSNull null] forKey:kCIContextWorkingColorSpace]; 311 | // [options setObject:@0 forKey:kCIContextUseSoftwareRenderer]; 312 | EAGLContext *eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; 313 | ciContext = [CIContext contextWithEAGLContext:eaglContext options:nil]; 314 | } 315 | [ciContext render:ciImage toCVPixelBuffer:pixbuffer]; 316 | // [ciContext render:ciImage toCVPixelBuffer:pixbuffer bounds:cropRect colorSpace:nil]; 317 | 318 | CMSampleTimingInfo sampleTime = { 319 | .duration = CMSampleBufferGetDuration(buffer), 320 | .presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(buffer), 321 | .decodeTimeStamp = CMSampleBufferGetDecodeTimeStamp(buffer) 322 | }; 323 | 324 | if (videoInfo == NULL) { 325 | status = CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixbuffer, &videoInfo); 326 | if (status != 0) NSLog(@"Crop CMVideoFormatDescriptionCreateForImageBuffer error %d",(int)status); 327 | } 328 | 329 | CMSampleBufferRef cropBuffer; 330 | status = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, pixbuffer, true, NULL, NULL, videoInfo, &sampleTime, &cropBuffer); 331 | if (status != 0) NSLog(@"Crop CMSampleBufferCreateForImageBuffer error %d",(int)status); 332 | 333 | return cropBuffer; 334 | } 335 | 336 | @end 337 | -------------------------------------------------------------------------------- /XDXCropSampleBuffer/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // XDXCropSampleBuffer 4 | // 5 | // Created by demon on 12/07/2017. 6 | // Copyright © 2017 demon. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /XDXCropSampleBufferTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /XDXCropSampleBufferTests/XDXCropSampleBufferTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDXCropSampleBufferTests.m 3 | // XDXCropSampleBufferTests 4 | // 5 | // Created by demon on 12/07/2017. 6 | // Copyright © 2017 demon. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface XDXCropSampleBufferTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation XDXCropSampleBufferTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /XDXCropSampleBufferUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /XDXCropSampleBufferUITests/XDXCropSampleBufferUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDXCropSampleBufferUITests.m 3 | // XDXCropSampleBufferUITests 4 | // 5 | // Created by demon on 12/07/2017. 6 | // Copyright © 2017 demon. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface XDXCropSampleBufferUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation XDXCropSampleBufferUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | --------------------------------------------------------------------------------