多媒體編程——ios視頻映像繪製工具類。
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 #import #import /* 渲染視頻,只支援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 #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 _fmEmptyQueue ; std::queue _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_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_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 @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