多媒体编程——ios视频图像绘制(2)

时间:2021-08-06 03:16:50

在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