如何計(jì)算當(dāng)前的幀率
在蘋果的官方demo中, 有一個(gè)很好的計(jì)算方法
...
NSMutableArray *_previousSecondTimestamps = [[NSMutableArray alloc] init];
...
- (void)calculateFramerateAtTimestamp:(CMTime)timestamp
{
[_previousSecondTimestamps addObject:[NSValue valueWithCMTime:timestamp]];
CMTime oneSecond = CMTimeMake( 1, 1 );
CMTime oneSecondAgo = CMTimeSubtract( timestamp, oneSecond );
while( CMTIME_COMPARE_INLINE( [_previousSecondTimestamps[0] CMTimeValue], <, oneSecondAgo ) ) {
[_previousSecondTimestamps removeObjectAtIndex:0];
}
if ( [_previousSecondTimestamps count] > 1 ) {
const Float64 duration = CMTimeGetSeconds( CMTimeSubtract( [[_previousSecondTimestamps lastObject] CMTimeValue], [_previousSecondTimestamps[0] CMTimeValue] ) );
const float newRate = (float)( [_previousSecondTimestamps count] - 1 ) / duration;
self.previewFrameRate = newRate;
NSLog(@"FrameRate - %f", newRate);
}
}
//在調(diào)用時(shí)候:
CMTime timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
[self calculateFramerateAtTimestamp:timestamp];
實(shí)時(shí)處理視頻幀過程中如何丟幀
iOS的AVCaptureSession的delegate回調(diào)方法- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
會(huì)把視頻幀和音頻幀通過該回調(diào)方法返回.
我們將視頻幀CMSampleBufferRef拋到自建線程中進(jìn)行實(shí)時(shí)處理, 該線程每次只處理一幀數(shù)據(jù), 處理過程中, 其他的視頻幀回調(diào)過來就丟棄, 具體的可以用以下兩種方法:
方法一, 使用static變量控制, 該處理線程,每次最多只能處理1幀數(shù)據(jù).如果當(dāng)前正在處理數(shù)據(jù), 則直接返回
static NSInteger nProcessFrame = 0;
if (nProcessFrame > 0) {
return; //last frame not finished
}
nProcessFrame ++;
__block CMSampleBufferRef currentBuffer;
CMSampleBufferCreateCopy(kCFAllocatorDefault, sampleBuffer, ¤tBuffer);
CMTime timestamp = CMSampleBufferGetPresentationTimeStamp( currentBuffer );
[self calculateFramerateAtTimestamp:timestamp];
dispatch_async(_bufferQueue, ^{
[self.delegate captureService:self processSampleBuffer:currentBuffer];
CFRelease(currentBuffer);
nProcessFrame1--;
return;
});
方法二, apple 的官方demo RosyWriter中的方法:
/**
// call under @synchronized( self )
注意這里使用的丟棄幀的方法.
在videoDataOutput 的 delegate queue中獲取 緩存一個(gè)previewPixelBuffer, 然后在外部輸出的_delegateCallbackQueue中獲取之前準(zhǔn)備好的pixelBuffer. 這樣可以丟棄部分內(nèi)容
主要由于 videoDataOutput queue 的速度 和delegatecallback queue 的速度決定的
*/
- (void)outputPreviewPixelBuffer:(CVPixelBufferRef)previewPixelBuffer {
// Keep preview latency low by dropping stale frames that have not been picked up by the delegate yet
// Note that access to currentPreviewPixelBuffer is protected by the @synchronized lock
// 通過丟棄那些還沒有被delegate處理的幀, 保證預(yù)覽的低延遲, 這里緩存一個(gè)pixelBuffer, 在delegate里面完成獲取要顯示的pixelBuffer
// 為什么這里注釋 - 可以通過丟幀來保證界面顯示的低預(yù)覽延遲.因?yàn)檫@里雖然使用self.currentPreviewPixelBuffer 獲取緩存了一個(gè)最近使用的pixelBuffer, 但是如果操作過快. 在進(jìn)入delegate queue 以后,有可能這個(gè)方法已經(jīng)調(diào)用了好幾次. 在delegate queue里面拿到的pixelbuffer 一定是最新的那一個(gè), 之前傳遞進(jìn)來的數(shù)據(jù)幀就被丟棄了!!!!!!!
self.currentPreviewPixelBuffer = previewPixelBuffer;
// 低速通道
[self invokeDelegateCallbackAsync:^{
CVPixelBufferRef currentPreviewPixelBuffer = NULL;
// 在delegate queue里面處理時(shí)候,對(duì)currentPreviewPixelBuffer上鎖. 在delegate queue中使用一個(gè)局部變量獲取之前緩存的那個(gè)previewPixelBuffer
// 這里給 self 加鎖,為了 獲取currentPreviewPixelBuffer
@synchronized(self) {
// 如果當(dāng)前有最優(yōu)的current previewPixelBuffer, 用一個(gè)local 變量獲取它,并把local變量拋出去
currentPreviewPixelBuffer = self.currentPreviewPixelBuffer;
if ( currentPreviewPixelBuffer ) {
CFRetain( currentPreviewPixelBuffer );
self.currentPreviewPixelBuffer = NULL;
}
}
if (currentPreviewPixelBuffer) {// 如果當(dāng)前存在最近一個(gè)的pixel buffer, 那么拋出去準(zhǔn)備完成界面繪制
// 通過delegate方法將準(zhǔn)備好的pixelBuffer 通過代理拋出去
[self.delegate captureService:self processSampleBuffer:currentBuffer];
CFRelease(currentPreviewPixelBuffer);
}
}];
}
CMSampleBufferRef轉(zhuǎn)化UIImage的性能問題
前一段時(shí)間在開發(fā)刷臉的過程中, 由于視頻幀(CMSampleBufferRef -> UIImage)性能瓶頸導(dǎo)致, 進(jìn)行實(shí)時(shí)的人臉檢測(cè)處理幀率太慢, 在性能較弱的機(jī)器比如iPhone4s, iPhone5中, 檢測(cè)幀率太低(不到10fps),導(dǎo)致(動(dòng)作活體, 反光活體)檢測(cè)效果不好.
在檢查了各個(gè)函數(shù)的運(yùn)行時(shí)間以后,確定了問題出現(xiàn)的原因: 視頻幀 -> UIImage 方法耗時(shí)嚴(yán)重, 因此導(dǎo)致幀率下降. 在優(yōu)圖原來使用的將視頻幀 -> 圖片的方法是用的CoreImage的函數(shù), 具體代碼如下:
-(UIImage *) imageFromImageBuffer:(CVImageBufferRef)imageBuffer{
CIImage *ciImage = [CIImage imageWithCVPixelBuffer:imageBuffer];
CGImageRef videoImage = [get_temp_context()
createCGImage:ciImage
fromRect:CGRectMake(0, 0,
CVPixelBufferGetWidth(imageBuffer),
CVPixelBufferGetHeight(imageBuffer))];
UIImage *image = [[UIImage alloc] initWithCGImage:videoImage scale:1.0f orientation:UIImageOrientationUpMirrored];
CGImageRelease(videoImage);
return image;
}
在使用該方法每幀數(shù)據(jù)耗時(shí)達(dá)100+ms, 因此單位時(shí)間內(nèi)處理例如轉(zhuǎn)化后的UIImage圖像時(shí), 檢測(cè)處理幀率會(huì)大幅減少, 測(cè)試結(jié)果顯示,iPhone5上動(dòng)作檢測(cè)只有10fps.
camera360的一個(gè)同行給出建議,切換成CoreGraphic相關(guān)函數(shù)能夠大幅降低處理時(shí)間, 具體代碼如下:
- (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer {
// 為媒體數(shù)據(jù)設(shè)置一個(gè)CMSampleBuffer的Core Video圖像緩存對(duì)象
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// 鎖定pixel buffer的基地址
CVPixelBufferLockBaseAddress(imageBuffer, 0);
// 得到pixel buffer的基地址
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
// 得到pixel buffer的行字節(jié)數(shù)
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
// 得到pixel buffer的寬和高
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
// 創(chuàng)建一個(gè)依賴于設(shè)備的RGB顏色空間
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// 用抽樣緩存的數(shù)據(jù)創(chuàng)建一個(gè)位圖格式的圖形上下文(graphics context)對(duì)象
CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8,
bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
// 根據(jù)這個(gè)位圖context中的像素?cái)?shù)據(jù)創(chuàng)建一個(gè)Quartz image對(duì)象
CGImageRef quartzImage = CGBitmapContextCreateImage(context);
// 解鎖pixel buffer
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
// 釋放context和顏色空間
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
// 用Quartz image創(chuàng)建一個(gè)UIImage對(duì)象image
UIImage *image = [UIImage imageWithCGImage:quartzImage];
// 釋放Quartz image對(duì)象
CGImageRelease(quartzImage);
return (image);
}
使用新方法以后, 每幀由視頻幀->圖片的處理時(shí)間降低一個(gè)數(shù)量級(jí), 在10ms左右, 大大提升了單位時(shí)間檢測(cè)效率.
CoreGraphic的應(yīng)用很廣泛, 基本是iOS中圖像繪制最棒的框架, 通常我們調(diào)整UIImage,例如改變大小寬度,旋轉(zhuǎn)重繪等等都會(huì)使用它.同時(shí)我們自定義控件, 例如創(chuàng)建一個(gè)圓形進(jìn)度條 - progressView,時(shí)候我們往往在drawRect中繪制當(dāng)前進(jìn)度等等.