在ios视频录像绘制(1)中演示了如何使用CALayer进行主动性的图像绘制。
对于帧率要求不高还可以,但是对于帧率很高的视频播放来说就很捉襟见肘。
一方面因为绘制工作交由主线程完成,主线程还要干别的事,忙不过来。
另一方面绘制的CGContextDrawImage里面有拉伸计算,速度快不起来。
有没有更快的绘制方式呢?当然是有的——OpenGLES
以前就知道OpenGLES可以用来简单的绘制视频图像,但是当时没有转过弯来(OpenGLES里面纹理尺寸必须是2的次数倍,而拉伸一张图片花得时间并不见得比前一种CALayer方式里面CGContextDrawImage所花的时间少,所以一直没有采用)。直到最近才找到了办法使用OpenGLES进行高效率的视频图像绘制方法
之前一直想的是基于OpenGLES,使用2D投影方式,构造一个和屏幕一样大的矩形,然后把纹理1:1贴图上去。最大的花费时间的地方在于拉伸图片以达到2的次数倍。
而实际上有更快的技巧,那就是先构造一个2的次数倍的纹理,比如1024x1024,然后拿到一个图像的buf,比如是540x360的,然后逐行拷贝图像到纹理buf,那么每一行的后1024-540这么多个像素认为是无用的,纹理的后1024-360这么多行 也认为是无用的,将纹理坐标设置1:1映射前面有效部分即可。
原来是这样,这样帧率可以达到30以上。。。
附上代码,头文件形式和前一章的一样,只是实现方式不一样。
//
// TKVideoPlayer.m
// FLVPlayer
//
// Created by administrator on 14-7-11.
// Copyright (c) 2014年 trustsky. All rights reserved.
//
#import "TKVideoPlayerGL.h"
#import "TKTimer.h"
#import "TKTimer2.h"
#import "TKLock.h"
#import "TKTicker.h"
#include <queue>
#define TKVIDEO_FRAME_CACHE_COUNT 8
/*
因为OpenGL只支持2的次数倍的尺寸纹理,
所以解决办法是创建一个符合条件的纹理缓存作为画布,
再将图像素具画到这个画布上,动态调整纹理坐标即可。
*/
#define TKVIDEO_TEXTURE_WIDTH 1024
#define TKVIDEO_TEXTURE_HEIGHT 1024
/*
OpenGL 支持的纹理RGBA格式,所以需要使用swscale转换像素格式。
*/
@interface TKVideoPlayerGL ()
{
//opengles
GLKView* _glView ;
EAGLContext* _context ;
GLuint _texture ;
NSData* _vertexs ;
NSData* _txcoods ;
//
uint8_t* _txtbuf ;
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 TKVideoPlayerGL
- (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->_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];
[self initOpenGLContext];
return true ;
}
- (bool) destory
{
[self uninitOpenGLContext];
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)initOpenGLContext
{
self->_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
self->_glView = [[GLKView alloc] initWithFrame: self->_view.bounds];
self->_glView.context = self->_context ;
self->_glView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888 ;
self->_glView.drawableDepthFormat = GLKViewDrawableDepthFormat24 ;
self->_glView.delegate = self ;
self->_glView.enableSetNeedsDisplay = false ;
[self->_view addSubview: self->_glView];
[EAGLContext setCurrentContext: self->_context]; //这个函数的位置很重要,后面的指令都作用于当前context。
float vertexArray[] = {
-1, 1, 0,
-1, -1, 0,
1, -1, 0,
-1, 1, 0,
1, -1, 0,
1, 1, 0,
};
float xrate = 1.0f*_width/TKVIDEO_TEXTURE_WIDTH ;
float yrate = 1.0f*_height/TKVIDEO_TEXTURE_HEIGHT ;
float textureArray[] = {
0, 0,
0, yrate,
xrate, yrate,
0, 0,
xrate, yrate,
xrate, 0
};
uint8_t* bufVertex = (uint8_t*)malloc(sizeof(vertexArray));
uint8_t* bufTxtCood = (uint8_t*)malloc(sizeof(textureArray));
memcpy(bufVertex, vertexArray, sizeof(vertexArray));
memcpy(bufTxtCood, textureArray, sizeof(textureArray));
_vertexs = [[NSData alloc] initWithBytesNoCopy:bufVertex length:sizeof(vertexArray) freeWhenDone:true ] ;
_txcoods = [[NSData alloc] initWithBytesNoCopy:bufTxtCood length:sizeof(textureArray) freeWhenDone:true ] ;
glEnable(GL_TEXTURE_2D);
glGenTextures(1, &_texture);
glBindTexture(GL_TEXTURE_2D, _texture);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) ;
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) ;
CGSize size = self->_glView.frame.size;
glViewport(0, 0, size.width, size.height); // 设置视口宽度高度。
// {修改投影矩阵
glMatrixMode(GL_PROJECTION); // 修改投影矩阵
glLoadIdentity(); // 复位,将投影矩阵归零
glOrthof(-1, 1, -1, 1, 1.0f, -1.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();//归零模型视图
GLKMatrix4 viewMatrix = GLKMatrix4MakeLookAt(0, 0, 1, 0, 0, 0, 0, 1, 0);
glMultMatrixf(viewMatrix.m);
//}
_txtbuf = (uint8_t*)malloc(TKVIDEO_TEXTURE_WIDTH * TKVIDEO_TEXTURE_HEIGHT * 4) ;
}
- (void)uninitOpenGLContext
{
free(_txtbuf);
[EAGLContext setCurrentContext:nil];
[_vertexs release];
[_txcoods release];
_vertexs = nil ;
_txcoods = nil ;
glDeleteTextures(1, &_texture);
[self->_glView removeFromSuperview];
[self->_glView release];
self->_glView = nil ;
[self->_context release];
self->_context = nil ;
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
[self glTexture];
glClear(GL_COLOR_BUFFER_BIT);// 清空相关缓存。
glClearColor(0, 0, 0, 0.0f); // 清空场景为黑色。
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glBindTexture(GL_TEXTURE_2D, _texture);
glTexCoordPointer(2, GL_FLOAT, 0, [_txcoods bytes]);
glVertexPointer(3, GL_FLOAT, 0, [_vertexs bytes]);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
- (void)glTexture
{
if(_state && _buffer)
{
uint32_t bytesPerRowA = _width * 4 ;
uint32_t bytesPerRowB = TKVIDEO_TEXTURE_WIDTH * 4 ;
for(uint32_t h=0; h<_height; h++)
memcpy(_txtbuf + h * bytesPerRowB, _buffer + h * bytesPerRowA, bytesPerRowA);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TKVIDEO_TEXTURE_WIDTH, TKVIDEO_TEXTURE_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, _txtbuf);
}
}
- (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->_buffer = cachebuf ;
[self timer_draw];
}
}
- (bool) timer_draw
{
if(_state && _buffer)
{
[self->_glView display];
[_lockEmptyQueue lock];
_fmEmptyQueue.push(self->_buffer);
[_lockEmptyQueue unlock];
dispatch_semaphore_signal(self->_sgEmpty);
}
else
{
}
return true ;
}
- (bool) clear
{
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