iOS開發(fā)中截取相機部分畫面,切割sampleBuffer(Crop sample buffer)

iOS開發(fā)中截取相機部分畫面食寡,切割sampleBuffer(Crop sample buffer)

本例需求:在類似直播的功能界面,二維碼掃描,人臉識別或其他需求中的功能界面或其他需求中需要從相機捕獲的畫面中單獨截取出一部分區(qū)域廓潜。


原理:由于需要截取相機捕獲整個畫面其中一部分抵皱,所以也就必須拿到那一部分畫面的數(shù)據(jù),又因為相機AVCaptureVideoDataOutputSampleBufferDelegate中的sampleBuffer為系統(tǒng)私有的數(shù)據(jù)結(jié)構(gòu)不可直接操作辩蛋,所以需要將其轉(zhuǎn)換成可以切割的數(shù)據(jù)結(jié)構(gòu)再進行切割呻畸,網(wǎng)上有種思路說將sampleBuffer間接轉(zhuǎn)換為UIImage再對圖片切割,這種思路繁瑣且性能低悼院,本例將sampleBuffer轉(zhuǎn)換為CoreImage中的CIImage,性能相對較高且降低代碼繁瑣度伤为。


最終效果如下, 綠色框中即為截圖的畫面据途,長按可以移動绞愚。

綠色框為截取部分

GitHub地址(附代碼) : Crop sample buffer

簡書地址 : Crop sample buffer

博客地址 : Crop sample buffer

掘金地址 : Crop sample buffer


注意:使用ARC與MRC下代碼有所區(qū)別,已經(jīng)在項目中標注好颖医,主要為管理全局的CIContext對象位衩,它在初始化的方法中編譯器沒有對其進行retain,所以,調(diào)用會報錯熔萧。

cicontextError

使用場景

  • 本項目中相機捕捉的背景分辨率默認設(shè)置為2K(即1920*1080)蚂四,可切換為4K ,所以需要iPhone 6s以上的設(shè)備才支持光戈。
  • 本例可以使用CPU/GPU切割,在VC中需要在cropView初始化前設(shè)置isOpenGPU的值遂赠,打開則使用GPU,否則CPU
  • 本例只實現(xiàn)了橫屏下的Crop功能久妆,本例默認始終為橫屏狀態(tài),未做豎屏處理跷睦。

基本配置

1.配置相機基本環(huán)境(初始化AVCaptureSession筷弦,設(shè)置代理,開啟)抑诸,在示例代碼中有烂琴,這里不再重復。

2.通過AVCaptureVideoDataOutputSampleBufferDelegate代理中拿到原始畫面數(shù)據(jù)(CMSampleBufferRef)進行處理

實現(xiàn)途徑

1.利用CPU軟件截取(CPU進行計算并切割蜕乡,消耗性能較大)

  • (CMSampleBufferRef)cropSampleBufferBySoftware:(CMSampleBufferRef)sampleBuffer奸绷;

2.利用 硬件截取(利用Apple官方公開的方法利用硬件進行切割,性能較好层玲, 但仍有問題待解決)

  • (CMSampleBufferRef)cropSampleBufferByHardware:(CMSampleBufferRef)buffer号醉;

解析

// Called whenever an AVCaptureVideoDataOutput instance outputs a new video frame. 每產(chǎn)生一幀視頻幀時調(diào)用一次
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    CMSampleBufferRef cropSampleBuffer;
    
#warning 兩種切割方式任選其一,GPU切割性能較好辛块,CPU切割取決于設(shè)備畔派,一般時間長會掉幀。
    if (self.isOpenGPU) {
         cropSampleBuffer = [self.cropView cropSampleBufferByHardware:sampleBuffer];
    }else {
         cropSampleBuffer = [self.cropView cropSampleBufferBySoftware:sampleBuffer];
    }
    
    // 使用完后必須顯式release润绵,不在iOS自動回收范圍
    CFRelease(cropSampleBuffer);
}

  • 以上方法為每產(chǎn)生一幀視頻幀時調(diào)用一次的相機代理线椰,其中sampleBuffer為每幀畫面的原始數(shù)據(jù),需要對原始數(shù)據(jù)進行切割處理方可達到本例需求尘盼。注意最后一定要對cropSampleBuffer進行release避免內(nèi)存溢出而發(fā)生閃退憨愉。

利用CPU截取

- (CMSampleBufferRef)cropSampleBufferBySoftware:(CMSampleBufferRef)sampleBuffer {
    OSStatus status;
    
    //    CVPixelBufferRef pixelBuffer = [self modifyImage:buffer];
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    // Lock the image buffer
    CVPixelBufferLockBaseAddress(imageBuffer,0);
    // Get information about the image
    uint8_t *baseAddress     = (uint8_t *)CVPixelBufferGetBaseAddress(imageBuffer);
    size_t  bytesPerRow      = CVPixelBufferGetBytesPerRow(imageBuffer);
    size_t  width            = CVPixelBufferGetWidth(imageBuffer);
    // size_t  height           = CVPixelBufferGetHeight(imageBuffer);
    NSInteger bytesPerPixel  =  bytesPerRow/width;
    
    // YUV 420 Rule
    if (_cropX % 2 != 0) _cropX += 1;
    NSInteger baseAddressStart = _cropY*bytesPerRow+bytesPerPixel*_cropX;
    static NSInteger lastAddressStart = 0;
    lastAddressStart = baseAddressStart;
    
    // pixbuffer 與 videoInfo 只有位置變換或者切換分辨率或者相機重啟時需要更新,其余情況不需要卿捎,Demo里只寫了位置更新莱衩,其余情況自行添加
    // NSLog(@"demon pix first : %zu - %zu - %@ - %d - %d - %d -%d",width, height, self.currentResolution,_cropX,_cropY,self.currentResolutionW,self.currentResolutionH);
    static CVPixelBufferRef            pixbuffer = NULL;
    static CMVideoFormatDescriptionRef videoInfo = NULL;
    
    // x,y changed need to reset pixbuffer and videoinfo
    if (lastAddressStart != baseAddressStart) {
        if (pixbuffer != NULL) {
            CVPixelBufferRelease(pixbuffer);
            pixbuffer = NULL;
        }
        
        if (videoInfo != NULL) {
            CFRelease(videoInfo);
            videoInfo = NULL;
        }
    }
    
    if (pixbuffer == NULL) {
        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithBool : YES],           kCVPixelBufferCGImageCompatibilityKey,
                                 [NSNumber numberWithBool : YES],           kCVPixelBufferCGBitmapContextCompatibilityKey,
                                 [NSNumber numberWithInt  : g_width_size],  kCVPixelBufferWidthKey,
                                 [NSNumber numberWithInt  : g_height_size], kCVPixelBufferHeightKey,
                                 nil];
        
        status = CVPixelBufferCreateWithBytes(kCFAllocatorDefault, g_width_size, g_height_size, kCVPixelFormatType_32BGRA, &baseAddress[baseAddressStart], bytesPerRow, NULL, NULL, (__bridge CFDictionaryRef)options, &pixbuffer);
        if (status != 0) {
            NSLog(@"Crop CVPixelBufferCreateWithBytes error %d",(int)status);
            return NULL;
        }
    }
    
    CVPixelBufferUnlockBaseAddress(imageBuffer,0);
    
    CMSampleTimingInfo sampleTime = {
        .duration               = CMSampleBufferGetDuration(sampleBuffer),
        .presentationTimeStamp  = CMSampleBufferGetPresentationTimeStamp(sampleBuffer),
        .decodeTimeStamp        = CMSampleBufferGetDecodeTimeStamp(sampleBuffer)
    };
    
    if (videoInfo == NULL) {
        status = CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixbuffer, &videoInfo);
        if (status != 0) NSLog(@"Crop CMVideoFormatDescriptionCreateForImageBuffer error %d",(int)status);
    }
    
    CMSampleBufferRef cropBuffer = NULL;
    status = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, pixbuffer, true, NULL, NULL, videoInfo, &sampleTime, &cropBuffer);
    if (status != 0) NSLog(@"Crop CMSampleBufferCreateForImageBuffer error %d",(int)status);
    
    lastAddressStart = baseAddressStart;
    
    return cropBuffer;
}

  • 以上方法為切割sampleBuffer的對象方法
    首先從CMSampleBufferRef中提取出CVImageBufferRef數(shù)據(jù)結(jié)構(gòu),然后對CVImageBufferRef進行加鎖處理娇澎,如果要進行頁面渲染笨蚁,需要一個和OpenGL緩沖兼容的圖像。用相機API創(chuàng)建的圖像已經(jīng)兼容趟庄,您可以馬上映射他們進行輸入括细。假設(shè)你從已有畫面中截取一個新的畫面,用作其他處理戚啥,你必須創(chuàng)建一種特殊的屬性用來創(chuàng)建圖像奋单。對于圖像的屬性必須有Crop寬高, 作為字典的Key.因此創(chuàng)建字典的關(guān)鍵幾步不可省略猫十。

位置的計算

在軟切中览濒,我們拿到一幀圖片的數(shù)據(jù)呆盖,通過遍歷其中的數(shù)據(jù)確定真正要Crop的位置,利用如下公式可求出具體位置贷笛,具體切割原理在[YUV介紹]中有提到应又,計算時所需的變量在以上代碼中均可得到。

 `NSInteger baseAddressStart = _cropY*bytesPerRow+bytesPerPixel*_cropX;
    `

注意:

  • 1.對X,Y坐標進行校正乏苦,因為CVPixelBufferCreateWithBytes是按照像素進行切割株扛,所以需要將點轉(zhuǎn)成像素,再按照比例算出當前位置汇荐。即為上述代碼的int cropX = (int)(currentResolutionW / kScreenWidth * self.cropView.frame.origin.x); currentResolutionW為當前分辨率的寬度洞就,kScreenWidth為屏幕實際寬度。
  • 2.根據(jù)YUV 420的規(guī)則掀淘,每4個Y共用1個UV,而一行有2個Y旬蟋,所以取點必須按照偶數(shù)取點。利用CPU切割中使用的方法為YUV分隔法革娄,具體切割方式請參考YUV介紹
  • 3.本例中聲明pixelBuffer與videoInfo均為靜態(tài)變量倾贰,為了節(jié)省每次創(chuàng)建浪費內(nèi)存,但是有三種情況需要重置它們:位置變化稠腊,分辨率改變躁染,重啟相機鸣哀。文章最后注意詳細提到架忌。
// hardware crop
- (CMSampleBufferRef)cropSampleBufferByHardware:(CMSampleBufferRef)buffer {
    // a CMSampleBuffer's CVImageBuffer of media data.
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(buffer);
    CGRect           cropRect    = CGRectMake(_cropX, _cropY, g_width_size, g_height_size);
    //        log4cplus_debug("Crop", "dropRect x: %f - y : %f - width : %zu - height : %zu", cropViewX, cropViewY, width, height);
    
    /*
     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.
      如果要進行頁面渲染,需要一個和OpenGL緩沖兼容的圖像我衬。用相機API創(chuàng)建的圖像已經(jīng)兼容叹放,您可以馬上映射他們進行輸入。假設(shè)你從已有畫面中截取一個新的畫面挠羔,用作其他處理井仰,你必須創(chuàng)建一種特殊的屬性用來創(chuàng)建圖像。對于圖像的屬性必須有kCVPixelBufferIOSurfacePropertiesKey 作為字典的Key.因此以下步驟不可省略
     */
    
    OSStatus status;
    
    /* Only resolution has changed we need to reset pixBuffer and videoInfo so that reduce calculate count */
    static CVPixelBufferRef            pixbuffer = NULL;
    static CMVideoFormatDescriptionRef videoInfo = NULL;
    
    if (pixbuffer == NULL) {
        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithInt:g_width_size],     kCVPixelBufferWidthKey,
                                 [NSNumber numberWithInt:g_height_size],    kCVPixelBufferHeightKey, nil];
        status = CVPixelBufferCreate(kCFAllocatorSystemDefault, g_width_size, g_height_size, kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, (__bridge CFDictionaryRef)options, &pixbuffer);
        // 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
        if (status != noErr) {
            NSLog(@"Crop CVPixelBufferCreate error %d",(int)status);
            return NULL;
        }
    }
    
    CIImage *ciImage = [CIImage imageWithCVImageBuffer:imageBuffer];
    ciImage = [ciImage imageByCroppingToRect:cropRect];
    // Ciimage get real image is not in the original point  after excute crop. So we need to pan.
    ciImage = [ciImage imageByApplyingTransform:CGAffineTransformMakeTranslation(-_cropX, -_cropY)];
    
    static CIContext *ciContext = nil;
    if (ciContext == nil) {
        //        NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
        //        [options setObject:[NSNull null] forKey:kCIContextWorkingColorSpace];
        //        [options setObject:@0            forKey:kCIContextUseSoftwareRenderer];
        EAGLContext *eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
        ciContext = [CIContext contextWithEAGLContext:eaglContext options:nil];
    }
    [ciContext render:ciImage toCVPixelBuffer:pixbuffer];
    //    [ciContext render:ciImage toCVPixelBuffer:pixbuffer bounds:cropRect colorSpace:nil];
    
    CMSampleTimingInfo sampleTime = {
        .duration               = CMSampleBufferGetDuration(buffer),
        .presentationTimeStamp  = CMSampleBufferGetPresentationTimeStamp(buffer),
        .decodeTimeStamp        = CMSampleBufferGetDecodeTimeStamp(buffer)
    };
    
    if (videoInfo == NULL) {
        status = CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixbuffer, &videoInfo);
        if (status != 0) NSLog(@"Crop CMVideoFormatDescriptionCreateForImageBuffer error %d",(int)status);
    }
    
    CMSampleBufferRef cropBuffer;
    status = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, pixbuffer, true, NULL, NULL, videoInfo, &sampleTime, &cropBuffer);
    if (status != 0) NSLog(@"Crop CMSampleBufferCreateForImageBuffer error %d",(int)status);
    
    return cropBuffer;
}

  • 以上為硬件切割的方法破加,硬件切割利用GPU進行切割俱恶,主要利用CoreImage中CIContext 對象進行渲染。

  • CoreImage and UIKit coordinates (CoreImage 與 UIKit坐標系問題):我在開始做的時候跟正常一樣用設(shè)定的位置對圖像進行切割范舀,但是發(fā)現(xiàn)合是,切出來的位置不對,通過上網(wǎng)查閱發(fā)現(xiàn)一個有趣的現(xiàn)象CoreImage 與 UIKit坐標系不相同
    如下圖:
    正常UIKit坐標系是以左上角為原點:

UIKit坐標系.png

而CoreImage坐標系是以左下角為原點:(在CoreImage中锭环,每個圖像的坐標系是獨立于設(shè)備的)

CoreImage坐標系.png

所以切割的時候一定要注意轉(zhuǎn)換Y聪全,X的位置是正確的,Y是相反的辅辩。

  • 如果要進行頁面渲染难礼,需要一個和OpenGL緩沖兼容的圖像娃圆。用相機API創(chuàng)建的圖像已經(jīng)兼容,您可以馬上映射他們進行輸入蛾茉。假設(shè)你從已有畫面中截取一個新的畫面讼呢,用作其他處理,你必須創(chuàng)建一種特殊的屬性用來創(chuàng)建圖像臀稚。對于圖像的屬性必須有寬高 作為字典的Key.因此創(chuàng)建字典的關(guān)鍵幾步不可省略吝岭。
  • 對CoreImage進行切割有兩種切割的方法均可用:
  1. ciImage = [ciImage imageByCroppingToRect:cropRect]; 如果使用此行代碼則渲染時用[ciContext render:ciImage toCVPixelBuffer:pixelBuffer];
  2. 或者直接使用: [ciContext render:ciImage toCVPixelBuffer:pixelBuffer bounds:cropRect colorSpace:nil];
  • 注意:CIContext 中包含圖像大量上下文信息,不能在回調(diào)中多次調(diào)用吧寺,官方建議只初始化一次窜管。但是注意ARC,MRC區(qū)別。

注意:

1. 使用ARC與MRC下代碼有所區(qū)別稚机,已經(jīng)在項目中標注好幕帆,主要為管理全局的CIContext對象,它在初始化的方法中編譯器沒有對其進行retain,所以赖条,調(diào)用會報錯失乾。
cicontextError
2.切換前后置攝像頭:因為不同機型的前后置攝像頭差別較大,一種處理手段是在記錄iphone機型crop的plist文件中增加前后置攝像頭支持分辨率的屬性纬乍,然后在代碼中根據(jù)plist映射出來的模型進行分別引用碱茁。另一種方案是做自動降級處理,例如后置支持2K仿贬,前置支持720P,則轉(zhuǎn)換后檢測到前置不支持2K就自動將前置降低一個等級纽竣,直到找到需要的等級。如果這樣操作處理邏輯較多且初看不易理解茧泪,而前置切割功能適用范圍不大蜓氨,所以暫時只支持后置切割。

補充說明

  • 屏幕邏輯分辨率與視頻分辨率
  1. Point and pixel的區(qū)別
    因為此類說明網(wǎng)上很多队伟,這里就不做太多具體闡述穴吹,僅僅簡述一下
    Point 即是設(shè)備的邏輯分辨率,即[UIScreen mainScreen].bounds.size.width 得到的設(shè)備的寬高嗜侮,所以點可以簡單理解為iOS開發(fā)中的坐標系港令,方便對界面元素進行描述。

  2. Pixel: 像素則是比點更精確的單位锈颗,在普通屏中1點=1像素顷霹,Retina屏中1點=2像素。

  3. 分辨率 分辨率需要根據(jù)不同機型所支持的最大分辨率進行設(shè)置宜猜,例如iPhone 6S以上機型支持4k(3840 * 2160)分辨率拍攝視頻泼返。而當我們進行Crop操作的時候調(diào)用的API正是通過像素來進行切割,所以我們操作的單位是pixel而不是point.下面會有詳細介紹姨拥。

  • ARC, MRC下所做工作不同

CIContext 的初始化

首先應(yīng)該將CIContext聲明為全局變量或靜態(tài)變量绅喉,因為CIContext初始化一次內(nèi)部含有大量信息渠鸽,比較耗內(nèi)存,且只是渲染的時候使用柴罐,無需每次都初始化徽缚,然后如下如果在MRC中初始化完成后并未對ciContext發(fā)出retain的消息,所以需要手動retain,但在ARC下系統(tǒng)會自動完成此操作革屠。

ARC:

static CIContext *ciContext = NULL;
ciContext = [CIContext contextWithOptions:nil];
MRC:

static CIContext *ciContext = NULL;
ciContext = [CIContext contextWithOptions:nil];
[ciContext retain];
  • 坐標問題
1. 理解點與像素的對應(yīng)關(guān)系

首先CropView需要在手機顯示出來凿试,所以坐標系還是UIKit的坐標系,左上角為原點似芝,寬高分別為不同手機的寬高(如iPhone8 : 375*667, iPhone8P : 414 * 736, iPhoneX : 375 * 816),但是我們需要算出實際分辨率下CropView的坐標那婉,即我們可以把當前獲取的cropView的x,y點的位置轉(zhuǎn)換成對應(yīng)pixel的位置。

// 注意這里求的是X的像素坐標党瓮,以iPhone 8 為例 (點為375 * 667)详炬,分辨率為(1920 * 1080)
_cropX  = (int)(_currentResolutionW / _screenWidth  * (cropView.frame.origin.x);
即
_cropX  = (int)(1920 / 375  * 當前cropView的x點坐標;
2. CPU / GPU 兩種方式切割時坐標系的位置不同

原點位置

CPU : UIKit為坐標系,原點在左上角

GPU : CoreImage為坐標系寞奸,原點在左下角

因此計算時如果使用GPU, y的坐標是相反的呛谜,我們需要通過如下公式轉(zhuǎn)換,即將點對應(yīng)轉(zhuǎn)為正常以左上角為原點坐標系中的點枪萄。
_cropY  = (int)(_currentResolutionH / _screenHeight * (_screenHeight - self.frame.origin.y  -  self.frame.size.height)); 
3. 當手機屏幕不是16:9時隐岛,如果將視頻設(shè)置為填充滿屏幕則會出現(xiàn)偏差

需要注意的是,因為部分手機或iPad屏幕尺寸并不為16:9(iPhone X, 所有iPad (4 : 3)),如果我們在2k(1920 * 1080) , 4k (3840 * 2160 ) 分辨率下對顯示的View設(shè)置了 captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; 那么屏幕會犧牲一部分視頻填充視圖瓷翻,即相機捕獲的視頻數(shù)據(jù)并沒有完整展現(xiàn)在手機視圖里聚凹,所以再使用我們的crop功能時,由于我們使用的是UIKit的坐標系逻悠,也就是說原點(0,0)并不是該幀圖片真正像素的(0,0)元践,而如果計算則需要寫很多額外代碼韭脊,所以我們可以在Crop功能下設(shè)置captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspect; 這樣的話video視圖會根據(jù)分辨率調(diào)整為顯示完整視頻童谒。但是設(shè)置后如果設(shè)備是iPhoneX (比例大于16:9,X軸會縮小,黑邊填充),iPad(比例小于16:9沪羔,y軸縮小饥伊,黑邊填充)。

按照如上解析蔫饰,我們之前計算的點會出現(xiàn)偏差琅豆,因為相當于x或y軸會縮小一部分,而我們拿到的cropView的坐標仍然是相對于整個父View而言篓吁。

這時茫因,如果我們通過不斷更改cropView則代碼量較大,所以我在這里定義了一個videoRect屬性用來記錄Video真正的Rect,因為當程序運行時我們可以得到屏幕寬高比例杖剪,所以通過確定寬高比可以拿到真正Video的rect,此時在后續(xù)代碼中我們只需要傳入videoRect的尺寸進行計算冻押,即時是原先正常16:9的手機后面API也無須更改驰贷。

4. 為什么用int

在軟切中,我們在創(chuàng)建pixelBuffer時需要使用

CV_EXPORT CVReturn CVPixelBufferCreateWithBytes(
   CFAllocatorRef CV_NULLABLE allocator,
   size_t width,
   size_t height,
   OSType pixelFormatType,
   void * CV_NONNULL baseAddress,
   size_t bytesPerRow,
   CVPixelBufferReleaseBytesCallback CV_NULLABLE releaseCallback,
   void * CV_NULLABLE releaseRefCon,
   CFDictionaryRef CV_NULLABLE pixelBufferAttributes,
   CV_RETURNS_RETAINED_PARAMETER CVPixelBufferRef CV_NULLABLE * CV_NONNULL pixelBufferOut)

這個API,我們需要將x,y的點放入baseAddress中洛巢,這里又需要使用公式NSInteger baseAddressStart = _cropY*bytesPerRow+bytesPerPixel*_cropX;,但是這里根據(jù)YUV 420的規(guī)則我們我們傳入的X的點不能為奇數(shù)括袒,所以我們需要if (_cropX % 2 != 0) _cropX += 1;,而只有整型才能求余稿茉,所以這里的點我們均定義為int,在視圖展示中忽略小數(shù)點的誤差锹锰。

TODO :

在硬件切割(GPU)的過程中發(fā)現(xiàn) [ciContext render:ciImage toCVPixelBuffer:pixelBuffer]; 渲染時間不斷增加,導致掉幀漓库,而ciContext只初始化一次恃慧,并未發(fā)生內(nèi)存泄露,如果input resolution 為 2k, 切割720P 在7plus上性能較好渺蒿,其他機型和尺寸則掉幀嚴重糕伐。而軟件切割(CPU)雖然CPU使用率相比GPU提高15%左右但是性能相對穩(wěn)定,掉幀也只有在長時間直播后偶爾發(fā)生蘸嘶,但是CPU使用率較高良瞧。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市训唱,隨后出現(xiàn)的幾起案子褥蚯,更是在濱河造成了極大的恐慌,老刑警劉巖况增,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赞庶,死亡現(xiàn)場離奇詭異,居然都是意外死亡澳骤,警方通過查閱死者的電腦和手機歧强,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來为肮,“玉大人摊册,你說我怎么就攤上這事〖昭蓿” “怎么了茅特?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長棋枕。 經(jīng)常有香客問我白修,道長,這世上最難降的妖魔是什么重斑? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任兵睛,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘祖很。我一直安慰自己累盗,他們只是感情好,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布突琳。 她就那樣靜靜地躺著若债,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拆融。 梳的紋絲不亂的頭發(fā)上蠢琳,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天,我揣著相機與錄音镜豹,去河邊找鬼傲须。 笑死,一個胖子當著我的面吹牛趟脂,可吹牛的內(nèi)容都是我干的泰讽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼昔期,長吁一口氣:“原來是場噩夢啊……” “哼已卸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起硼一,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤累澡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后般贼,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愧哟,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年哼蛆,在試婚紗的時候發(fā)現(xiàn)自己被綠了蕊梧。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡腮介,死狀恐怖肥矢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情萤厅,我是刑警寧澤橄抹,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布靴迫,位于F島的核電站惕味,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏玉锌。R本人自食惡果不足惜名挥,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望主守。 院中可真熱鬧禀倔,春花似錦榄融、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鞋既,卻和暖如春力九,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背邑闺。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工跌前, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人陡舅。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓抵乓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親靶衍。 傳聞我的和親對象是個殘疾皇子灾炭,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359