IOS上视频级的图像绘制
ios上的图像绘制常规的是 UIView的drawRect函数,但是这个函数是异步触发,并且由主线程执行。虽然可以通过一定技巧达到主动绘制的效果:
1、传递图像给UIView缓存着。
2、然后调用UIView的setNeedDisplay 改写重绘标志。
(以上两步是讲图像丢给UIView,让它自己进行绘制,但是绘制的时机不可控,有时候我们需要它马上绘制,甚至有时候我们需要知道它什么时候绘制完成了,就需要下面两步)
3、在播放线程中调用UIView的 perfromOnMainThread 最后一个参数 waitUtilDone = true, 执行某个自定义函数比如叫 mydrawImage
4、mydrawImage中 调用 【NSRunloop mainLoop】run 。。。。 (执行一次消息泵抽送)
(这样调用perfromOnMainThread的地方就会阻塞,知道真正的绘制完成。)
但是这种方式的模拟同步方式绘制 知识等待主线程绘制完成,并且如果扩展到多帧缓存 就比较麻烦了,并且UIView必须自己继承然后重写。
下面附上代码 基于类似思想,但是是基于CALayer 完成的渲染工具类,同步和异步切换只需要改一下 waitUtilDone的参数即可。
实际测试 帧率可以达到25左右 (ipad mini1),如果机器好点 速度应该更快。
头文件
#import <Foundation/Foundation.h> #import <GLKit/GLKit.h> #import <stdint.h> /* 渲染视频,只支持RGB RGB RGB 32bit格式。 */ @interface TKVideoPlayer : NSObject - (bool) create:(UIView*)target width:(uint16_t)width height:(uint16_t)height frate:(float)frate ; - (bool) destory ; - (bool) update:(uint8_t*)buf len:(uint32_t) len ; - (bool) start ; - (bool) stop ; @end
实现文件
// // TKVideoPlayer.m // FLVPlayer // // Created by administrator on 14-7-11. // Copyright (c) 2014年 trustsky. All rights reserved. // #import "TKVideoPlayer.h" #import "TKTimer.h" #import "TKTimer2.h" #import "TKLock.h" #import "TKTicker.h" #include <queue> #define TKVIDEO_FRAME_CACHE_COUNT 8 @interface TKVideoPlayer () { UIView* _view ; float _frate ; uint16_t _width ; uint16_t _height ; uint8_t* _buffer ; uint32_t _length ; TKTimer2* _timer ; bool _state ; TKLock* _lockEmptyQueue ; TKLock* _lockFilledQueue ; std::queue<uint8_t*> _fmEmptyQueue ; std::queue<uint8_t*> _fmFiledQueue ; uint8_t* _fmbuffr[TKVIDEO_FRAME_CACHE_COUNT]; dispatch_semaphore_t _sgEmpty ; dispatch_semaphore_t _sgfilled ; } @end @implementation TKVideoPlayer - (bool) create:(UIView*)target width:(uint16_t)width height:(uint16_t)height frate:(float)frate; { self->_view = target ; self->_width = width ; self->_height = height ; self->_frate = frate ; self->_length = width * height * 4 ; self->_view.layer.delegate = self ; self->_sgfilled = dispatch_semaphore_create(TKVIDEO_FRAME_CACHE_COUNT); self->_sgEmpty = dispatch_semaphore_create(TKVIDEO_FRAME_CACHE_COUNT); for(int idx=0; idx<TKVIDEO_FRAME_CACHE_COUNT; idx++) { _fmbuffr[idx] = (uint8_t*)malloc(_length) ; _fmEmptyQueue.push(_fmbuffr[idx]); dispatch_semaphore_wait(_sgfilled, DISPATCH_TIME_FOREVER); } self->_lockFilledQueue = [[TKLock alloc] init]; [self->_lockFilledQueue open]; self->_lockEmptyQueue = [[TKLock alloc] init]; [self->_lockEmptyQueue open]; return true ; } - (bool) destory { self->_view.layer.delegate = nil ; self->_view = nil ; self->_buffer = NULL ; for(int idx=0; idx<TKVIDEO_FRAME_CACHE_COUNT; idx++) { free(_fmbuffr[idx]) ; _fmbuffr[idx] = NULL ; } [self->_lockFilledQueue close]; [self->_lockFilledQueue release]; self->_lockFilledQueue = nil ; [self->_lockEmptyQueue close]; [self->_lockEmptyQueue release]; self->_lockEmptyQueue = nil ; int lastCount = TKVIDEO_FRAME_CACHE_COUNT - _fmEmptyQueue.size() - _fmFiledQueue.size() ; for(int idx=0; idx<_fmEmptyQueue.size()+lastCount; idx++) dispatch_semaphore_signal(self->_sgfilled); for(int idx=0; idx<_fmFiledQueue.size()+lastCount; idx++) dispatch_semaphore_signal(self->_sgEmpty); dispatch_release(self->_sgfilled); self->_sgfilled = nil ; dispatch_release(self->_sgEmpty); self->_sgEmpty = nil ; return true ; } - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context { //计算图像居中应该的尺寸 CGRect frame = [layer bounds]; float scalew = frame.size.width/_width ; float scaleh = frame.size.height/_height; float scale = scalew < scaleh ? scalew : scaleh ; float image_width = _width * scale ; float image_height = _height * scale ; CGRect rect = CGRectMake((frame.size.width - image_width)/2, (frame.size.height - image_height)/2, image_width, image_height); if(_state && _buffer) { CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, _buffer, _length, NULL); CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB() ; CGImageRef imageRef = CGImageCreate(_width, _height, 8, 32, 4 * _width, colorSpaceRef, kCGBitmapByteOrder32Little|kCGImageAlphaFirst, provider, NULL, NO, kCGRenderingIntentDefault); CGContextTranslateCTM(context, 0.0, frame.size.height); CGContextScaleCTM(context, 1.0, -1.0); CGContextDrawImage(context, rect, imageRef); CGImageRelease(imageRef); CGColorSpaceRelease(colorSpaceRef); CGDataProviderRelease(provider); //NSLog(@"drawLayer Time Tick = %u.", get_tick32()); } else { CGContextSetRGBFillColor(context, 0, 0, 0, 1); CGContextFillRect(context, frame); } } - (bool) update:(uint8_t*)buf len:(uint32_t) len { if(_state) { dispatch_semaphore_wait(_sgEmpty, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_MSEC* 100)); [_lockEmptyQueue lock]; if(_fmEmptyQueue.size() == 0) { [_lockEmptyQueue unlock]; return true; } uint8_t* cachebuf = _fmEmptyQueue.front(); _fmEmptyQueue.pop(); [_lockEmptyQueue unlock]; memcpy(cachebuf, buf, len); [_lockFilledQueue lock]; _fmFiledQueue.push(cachebuf); [_lockFilledQueue unlock]; dispatch_semaphore_signal(self->_sgfilled); } return true ; } - (void) timer_call { if(_state) { dispatch_semaphore_wait(self->_sgfilled, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_MSEC*100)); //等待100毫秒 [_lockFilledQueue lock]; if(_fmFiledQueue.size() == 0) { [_lockFilledQueue unlock]; return ; } uint8_t* cachebuf = _fmFiledQueue.front(); _fmFiledQueue.pop(); [_lockFilledQueue unlock]; [self performSelectorOnMainThread:@selector(timerDrawFrame:) withObject:[NSNumber numberWithUnsignedLongLong:(uint64_t)cachebuf] waitUntilDone:false]; } } - (void) timerDrawFrame:(NSNumber*)bufNumber { self->_buffer = (uint8_t*)bufNumber.unsignedLongLongValue ; if(_state && _buffer) { [self->_view.layer setNeedsDisplay]; [self->_view.layer display]; [_lockEmptyQueue lock]; _fmEmptyQueue.push(self->_buffer); [_lockEmptyQueue unlock]; dispatch_semaphore_signal(self->_sgEmpty); } else { [self->_view.layer setNeedsDisplay]; [self->_view.layer display]; } } - (bool) clear { [self performSelectorOnMainThread:@selector(timerDrawFrame:) withObject:[NSNumber numberWithUnsignedLongLong:NULL] waitUntilDone:true]; return true ; } - (bool) start { if(_timer == nil) { _timer = [[TKTimer2 alloc] init]; _timer.delay = 1000/_frate ; _timer.objcall = self ; _timer.selcall = @selector(timer_call); [_timer start]; _state = true ; } return true ; } - (bool) stop { if(_timer) { _state = false ; [_timer stop]; [self clear]; } return true ; } @end
//里面用到了一个 TKTimer计时器
计时器的头文件是这样的
@interface TKTimer2 : NSObject @property (assign, nonatomic) id objcall ; @property (assign, nonatomic) SEL selcall ; @property (assign, nonatomic) uint32_t delay ; - (void) start ; - (void) stop ; @end
设置回调的id + SEL 然后设置延迟 毫秒单位,调用start之后该id+sel会被重复执行。本人还在调研那种计时效果准确,所以就不发上来误导大家了,大家自己实现吧
还用到了一个TKLock
#import <Foundation/Foundation.h> @interface TKLock : NSObject - (void)open ; - (void)close ; - (void)lock ; - (void)unlock ; - (bool)trylock:(uint32_t)tick ; @end
实现如下:
#import "TKLock.h" @interface TKLock () { dispatch_semaphore_t _sglock ; //是否缓存为空 } @end @implementation TKLock - (void)open { _sglock = dispatch_semaphore_create(1); } - (void)close { [self trylock:1]; dispatch_semaphore_signal(_sglock); dispatch_release(_sglock); _sglock = nil ; } - (void)lock { dispatch_semaphore_wait(_sglock, DISPATCH_TIME_FOREVER); } - (void)unlock { dispatch_semaphore_signal(_sglock); } - (bool)trylock:(uint32_t)tick { long retcode = dispatch_semaphore_wait(_sglock, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_MSEC*tick)); return (retcode == 0) ; } @end